mirror of
https://github.com/XRPLF/rippled.git
synced 2026-06-03 00:36:48 +00:00
The fuzzing server and client are intended to mimic the Autobahn versions and implement only the 9.* tests to enable performance testing of implementations faster than AB
461 lines
14 KiB
C++
461 lines
14 KiB
C++
/*
|
|
* Copyright (c) 2011, Peter Thorson. All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions are met:
|
|
* * Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* * 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.
|
|
* * Neither the name of the WebSocket++ Project 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 PETER THORSON 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 WEBSOCKETPP_ROLE_CLIENT_HPP
|
|
#define WEBSOCKETPP_ROLE_CLIENT_HPP
|
|
|
|
#include "../endpoint.hpp"
|
|
#include "../uri.hpp"
|
|
|
|
#include <boost/asio.hpp>
|
|
#include <boost/bind.hpp>
|
|
#include <boost/shared_ptr.hpp>
|
|
#include <boost/random.hpp>
|
|
#include <boost/random/random_device.hpp>
|
|
|
|
#include <iostream>
|
|
|
|
using boost::asio::ip::tcp;
|
|
|
|
namespace websocketpp {
|
|
namespace role {
|
|
|
|
template <class endpoint>
|
|
class client {
|
|
public:
|
|
// Connection specific details
|
|
template <typename connection_type>
|
|
class connection {
|
|
public:
|
|
typedef connection<connection_type> type;
|
|
typedef endpoint endpoint_type;
|
|
|
|
// client connections are friends with their respective client endpoint
|
|
friend class client<endpoint>;
|
|
|
|
// Valid always
|
|
int get_version() const {
|
|
return m_version;
|
|
}
|
|
|
|
std::string get_origin() const {
|
|
return m_origin;
|
|
}
|
|
|
|
// not sure when valid
|
|
std::string get_request_header(const std::string& key) const {
|
|
return m_request.header(key);
|
|
}
|
|
std::string get_response_header(const std::string& key) const {
|
|
return m_response.header(key);
|
|
}
|
|
|
|
|
|
// Information about the requested URI
|
|
// valid only after URIs are loaded
|
|
// TODO: check m_uri for NULLness
|
|
bool get_secure() const {
|
|
return m_uri->get_secure();
|
|
}
|
|
std::string get_host() const {
|
|
return m_uri->get_host();
|
|
}
|
|
std::string get_resource() const {
|
|
return m_uri->get_resource();
|
|
}
|
|
uint16_t get_port() const {
|
|
return m_uri->get_port();
|
|
}
|
|
|
|
int32_t rand() {
|
|
return m_endpoint.rand();
|
|
}
|
|
protected:
|
|
connection(endpoint& e)
|
|
: m_endpoint(e),
|
|
m_connection(static_cast< connection_type& >(*this)),
|
|
// TODO: version shouldn't be hardcoded
|
|
m_version(13) {}
|
|
|
|
void set_uri(uri_ptr u) {
|
|
m_uri = u;
|
|
}
|
|
|
|
void async_init() {
|
|
m_connection.m_processor = processor::ptr(new processor::hybi<connection_type>(m_connection));
|
|
|
|
write_request();
|
|
}
|
|
|
|
|
|
|
|
void write_request();
|
|
void handle_write_request(const boost::system::error_code& error);
|
|
void read_response();
|
|
void handle_read_response(const boost::system::error_code& error,
|
|
std::size_t bytes_transferred);
|
|
|
|
void log_open_result();
|
|
private:
|
|
endpoint& m_endpoint;
|
|
connection_type& m_connection;
|
|
|
|
int m_version;
|
|
uri_ptr m_uri;
|
|
std::string m_origin;
|
|
std::vector<std::string> m_requested_subprotocols;
|
|
std::vector<std::string> m_requested_extensions;
|
|
std::string m_subprotocol;
|
|
std::vector<std::string> m_extensions;
|
|
|
|
std::string m_handshake_key;
|
|
http::parser::request m_request;
|
|
http::parser::response m_response;
|
|
};
|
|
|
|
// types
|
|
typedef client<endpoint> type;
|
|
typedef endpoint endpoint_type;
|
|
|
|
typedef typename endpoint_traits<endpoint>::connection_ptr connection_ptr;
|
|
typedef typename endpoint_traits<endpoint>::handler_ptr handler_ptr;
|
|
|
|
// handler interface callback class
|
|
class handler_interface {
|
|
public:
|
|
// Required
|
|
virtual void on_open(connection_ptr connection) {};
|
|
virtual void on_close(connection_ptr connection) {};
|
|
virtual void on_fail(connection_ptr connection) {}
|
|
|
|
virtual void on_message(connection_ptr connection,message::data_ptr) {};
|
|
|
|
// Optional
|
|
virtual bool on_ping(connection_ptr connection,std::string) {return true;}
|
|
virtual void on_pong(connection_ptr connection,std::string) {}
|
|
|
|
};
|
|
|
|
client (boost::asio::io_service& m)
|
|
: m_state(UNINITIALIZED),
|
|
m_endpoint(static_cast< endpoint_type& >(*this)),
|
|
m_io_service(m),
|
|
m_resolver(m),
|
|
m_gen(m_rng,boost::random::uniform_int_distribution<>(INT32_MIN,
|
|
INT32_MAX)) {}
|
|
|
|
connection_ptr connect(const std::string& u);
|
|
|
|
// TODO: add a `perpetual` option
|
|
void run() {
|
|
m_io_service.run();
|
|
}
|
|
|
|
void reset() {
|
|
m_io_service.reset();
|
|
}
|
|
|
|
protected:
|
|
bool is_server() {
|
|
return false;
|
|
}
|
|
int32_t rand() {
|
|
return m_gen();
|
|
}
|
|
private:
|
|
enum state {
|
|
UNINITIALIZED = 0,
|
|
INITIALIZED = 1,
|
|
CONNECTING = 2,
|
|
CONNECTED = 3
|
|
};
|
|
|
|
void handle_connect(connection_ptr con,
|
|
const boost::system::error_code& error);
|
|
|
|
state m_state;
|
|
endpoint_type& m_endpoint;
|
|
boost::asio::io_service& m_io_service;
|
|
tcp::resolver m_resolver;
|
|
|
|
boost::random::random_device m_rng;
|
|
boost::random::variate_generator<
|
|
boost::random::random_device&,
|
|
boost::random::uniform_int_distribution<>
|
|
> m_gen;
|
|
};
|
|
|
|
// client implimentation
|
|
|
|
template <class endpoint>
|
|
typename endpoint_traits<endpoint>::connection_ptr
|
|
client<endpoint>::connect(const std::string& u) {
|
|
// TODO: will throw, should we catch and wrap?
|
|
uri_ptr location(new uri(u));
|
|
|
|
if (location->get_secure() && !m_endpoint.is_secure()) {
|
|
// TODO: what kind of exception does client throw?
|
|
throw "";
|
|
}
|
|
|
|
tcp::resolver::query query(location->get_host(),location->get_port_str());
|
|
tcp::resolver::iterator iterator = m_resolver.resolve(query);
|
|
|
|
connection_ptr con = m_endpoint.create_connection();
|
|
con->set_uri(location);
|
|
|
|
boost::asio::async_connect(
|
|
con->get_raw_socket(),
|
|
iterator,
|
|
boost::bind(
|
|
&endpoint_type::handle_connect,
|
|
this, // shared from this?
|
|
con,
|
|
boost::asio::placeholders::error
|
|
)
|
|
);
|
|
m_state = CONNECTING;
|
|
|
|
return con;
|
|
}
|
|
|
|
template <class endpoint>
|
|
void client<endpoint>::handle_connect(connection_ptr con,
|
|
const boost::system::error_code& error)
|
|
{
|
|
if (!error) {
|
|
m_endpoint.alog().at(log::alevel::CONNECT)
|
|
<< "Successful connection" << log::endl;
|
|
|
|
m_state = CONNECTED;
|
|
con->start();
|
|
} else {
|
|
m_endpoint.elog().at(log::elevel::ERROR)
|
|
<< "An error occurred while establishing a connection: "
|
|
<< error << log::endl;
|
|
|
|
// TODO: fix
|
|
throw "client error";
|
|
}
|
|
}
|
|
|
|
// client connection implimentation
|
|
|
|
template <class endpoint>
|
|
template <class connection_type>
|
|
void client<endpoint>::connection<connection_type>::write_request() {
|
|
// async write to handle_write
|
|
|
|
m_request.set_method("GET");
|
|
m_request.set_uri(m_uri->get_resource());
|
|
m_request.set_version("HTTP/1.1");
|
|
|
|
m_request.add_header("Upgrade","websocket");
|
|
m_request.add_header("Connection","Upgrade");
|
|
m_request.replace_header("Sec-WebSocket-Version","13");
|
|
|
|
m_request.replace_header("Host",m_uri->get_host_port());
|
|
|
|
if (m_origin != "") {
|
|
m_request.replace_header("Origin",m_origin);
|
|
}
|
|
|
|
// Generate client key
|
|
int32_t raw_key[4];
|
|
|
|
for (int i = 0; i < 4; i++) {
|
|
raw_key[i] = this->rand();
|
|
}
|
|
|
|
m_handshake_key = base64_encode(reinterpret_cast<unsigned char const*>(raw_key), 16);
|
|
|
|
m_request.replace_header("Sec-WebSocket-Key",m_handshake_key);
|
|
m_request.replace_header("User Agent","WebSocket++/2011-12-06");
|
|
|
|
std::string raw = m_request.raw();
|
|
|
|
m_endpoint.alog().at(log::alevel::DEBUG_HANDSHAKE) << raw << log::endl;
|
|
|
|
boost::asio::async_write(
|
|
m_connection.get_socket(),
|
|
boost::asio::buffer(raw),
|
|
boost::bind(
|
|
&type::handle_write_request,
|
|
m_connection.shared_from_this(),
|
|
boost::asio::placeholders::error
|
|
)
|
|
);
|
|
}
|
|
|
|
template <class endpoint>
|
|
template <class connection_type>
|
|
void client<endpoint>::connection<connection_type>::handle_write_request(
|
|
const boost::system::error_code& error)
|
|
{
|
|
if (error) {
|
|
m_endpoint.elog().at(log::elevel::ERROR) << "Error writing WebSocket request. code: " << error << log::endl;
|
|
m_connection.terminate(false);
|
|
return;
|
|
|
|
}
|
|
|
|
read_response();
|
|
}
|
|
|
|
template <class endpoint>
|
|
template <class connection_type>
|
|
void client<endpoint>::connection<connection_type>::read_response() {
|
|
boost::asio::async_read_until(
|
|
m_connection.get_socket(),
|
|
m_connection.buffer(),
|
|
"\r\n\r\n",
|
|
boost::bind(
|
|
&type::handle_read_response,
|
|
m_connection.shared_from_this(),
|
|
boost::asio::placeholders::error,
|
|
boost::asio::placeholders::bytes_transferred
|
|
)
|
|
);
|
|
}
|
|
|
|
template <class endpoint>
|
|
template <class connection_type>
|
|
void client<endpoint>::connection<connection_type>::handle_read_response (
|
|
const boost::system::error_code& error, std::size_t bytes_transferred)
|
|
{
|
|
if (error) {
|
|
m_endpoint.elog().at(log::elevel::ERROR) << "Error reading HTTP request. code: " << error << log::endl;
|
|
m_connection.terminate(false);
|
|
return;
|
|
}
|
|
|
|
try {
|
|
std::istream request(&m_connection.buffer());
|
|
|
|
if (!m_response.parse_complete(request)) {
|
|
// not a valid HTTP response
|
|
// TODO: this should be a client error
|
|
throw http::exception("Could not parse server response.",http::status_code::BAD_REQUEST);
|
|
}
|
|
|
|
m_endpoint.alog().at(log::alevel::DEBUG_HANDSHAKE) << m_response.raw() << log::endl;
|
|
|
|
// error checking
|
|
if (m_response.get_status_code() != http::status_code::SWITCHING_PROTOCOLS) {
|
|
throw http::exception("Server failed to upgrade connection.",
|
|
m_response.get_status_code(),
|
|
m_response.get_status_msg());
|
|
}
|
|
|
|
std::string h = m_response.header("Upgrade");
|
|
if (!boost::ifind_first(h,"websocket")) {
|
|
throw http::exception("Token `websocket` missing from Upgrade header.",
|
|
m_response.get_status_code(),
|
|
m_response.get_status_msg());
|
|
}
|
|
|
|
h = m_response.header("Connection");
|
|
if (!boost::ifind_first(h,"upgrade")) {
|
|
throw http::exception("Token `upgrade` missing from Connection header.",
|
|
m_response.get_status_code(),
|
|
m_response.get_status_msg());
|
|
}
|
|
|
|
h = m_response.header("Sec-WebSocket-Accept");
|
|
if (h == "") {
|
|
throw http::exception("Required Sec-WebSocket-Accept header is missing.",
|
|
m_response.get_status_code(),
|
|
m_response.get_status_msg());
|
|
} else {
|
|
std::string server_key = m_handshake_key;
|
|
server_key += "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
|
|
|
SHA1 sha;
|
|
uint32_t message_digest[5];
|
|
|
|
sha.Reset();
|
|
sha << server_key.c_str();
|
|
|
|
if (!sha.Result(message_digest)) {
|
|
m_endpoint.elog().at(log::elevel::ERROR) << "Error computing handshake sha1 hash." << log::endl;
|
|
// TODO: close behavior
|
|
return;
|
|
}
|
|
|
|
// convert sha1 hash bytes to network byte order because this sha1
|
|
// library works on ints rather than bytes
|
|
for (int i = 0; i < 5; i++) {
|
|
message_digest[i] = htonl(message_digest[i]);
|
|
}
|
|
|
|
server_key = base64_encode(
|
|
reinterpret_cast<const unsigned char*>(message_digest),20);
|
|
if (server_key != h) {
|
|
m_endpoint.elog().at(log::elevel::ERROR) << "Server returned incorrect handshake key." << log::endl;
|
|
// TODO: close behavior
|
|
return;
|
|
}
|
|
}
|
|
|
|
log_open_result();
|
|
|
|
m_connection.m_state = session::state::OPEN;
|
|
|
|
m_endpoint.get_handler()->on_open(m_connection.shared_from_this());
|
|
|
|
m_connection.handle_read_frame(boost::system::error_code());
|
|
} catch (const http::exception& e) {
|
|
m_endpoint.elog().at(log::elevel::ERROR)
|
|
<< "Error processing server handshake. Server HTTP response: "
|
|
<< e.m_error_msg << " (" << e.m_error_code
|
|
<< ") Local error: " << e.what() << log::endl;
|
|
return;
|
|
}
|
|
|
|
|
|
// start session loop
|
|
}
|
|
|
|
template <class endpoint>
|
|
template <class connection_type>
|
|
void client<endpoint>::connection<connection_type>::log_open_result() {
|
|
/*std::stringstream version;
|
|
version << "v" << m_version << " ";
|
|
|
|
m_endpoint.alog().at(log::alevel::CONNECT) << (m_version == -1 ? "HTTP" : "WebSocket") << " Connection "
|
|
<< m_connection.get_raw_socket().remote_endpoint() << " "
|
|
<< (m_version == -1 ? "" : version.str())
|
|
<< (get_request_header("User-Agent") == "" ? "NULL" : get_request_header("User-Agent"))
|
|
<< " " << m_uri->get_resource() << " " << m_response.get_status_code()
|
|
<< log::endl;*/
|
|
}
|
|
|
|
} // namespace role
|
|
} // namespace websocketpp
|
|
|
|
#endif // WEBSOCKETPP_ROLE_CLIENT_HPP
|