diff --git a/src/server/poolserver/CMakeLists.txt b/src/server/poolserver/CMakeLists.txt index e437a16..624e82e 100644 --- a/src/server/poolserver/CMakeLists.txt +++ b/src/server/poolserver/CMakeLists.txt @@ -15,6 +15,8 @@ include_directories( ${CMAKE_SOURCE_DIR}/src/server/shared/Configuration ${CMAKE_SOURCE_DIR}/src/server/shared/Database ${CMAKE_SOURCE_DIR}/src/server/shared/Logging + ${CMAKE_SOURCE_DIR}/src/server/shared/JSON + ${CMAKE_SOURCE_DIR}/src/server/shared/JSONRPC ${CMAKE_SOURCE_DIR}/src/server/poolserver/Server ${CMAKE_SOURCE_DIR}/src/server/poolserver/Database ${Boost_INCLUDE_DIR} diff --git a/src/server/poolserver/Main.cpp b/src/server/poolserver/Main.cpp index cf428ac..934fef1 100644 --- a/src/server/poolserver/Main.cpp +++ b/src/server/poolserver/Main.cpp @@ -1,9 +1,14 @@ #include "Server.h" #include "Config.h" #include "Log.h" +#include "Util.h" +#include "JSON.h" +#include "JSONRPC.h" #include #include #include +#include +#include bool InitConfig(int argc, char *argv[]) { @@ -78,7 +83,7 @@ bool InitConfig(int argc, char *argv[]) if (!ifs.is_open()) { sLog.Error(LOG_GENERAL, "Failed opening config file: %s", sConfig.Get("config").c_str()); - return false; + return true; } store(parse_config_file(ifs, fileOptions), sConfig.vm); diff --git a/src/server/shared/CMakeLists.txt b/src/server/shared/CMakeLists.txt index cf7017a..00296da 100644 --- a/src/server/shared/CMakeLists.txt +++ b/src/server/shared/CMakeLists.txt @@ -4,6 +4,8 @@ add_subdirectory(Database) file(GLOB_RECURSE sources_Configuration Configuration/*.cpp Configuration/*.h) file(GLOB_RECURSE sources_Logging Logging/*.cpp Logging/*.h) file(GLOB_RECURSE sources_Stratum Stratum/*.cpp Stratum/*.h) +file(GLOB_RECURSE sources_JSON JSON/*.cpp JSON/*.h) +file(GLOB_RECURSE sources_JSONRPC JSONRPC/*.cpp JSONRPC/*.h) file(GLOB sources_localdir *.cpp *.h) @@ -12,6 +14,8 @@ set(sources_Shared ${sources_Database} ${sources_Logging} ${sources_Stratum} + ${sources_JSON} + ${sources_JSONRPC} ${sources_localdir} ) @@ -20,6 +24,8 @@ include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/Configuration ${CMAKE_CURRENT_SOURCE_DIR}/Database ${CMAKE_CURRENT_SOURCE_DIR}/Logging + ${CMAKE_CURRENT_SOURCE_DIR}/JSON + ${CMAKE_CURRENT_SOURCE_DIR}/JSONRPC ${Boost_INCLUDE_DIR} ${MYSQL_INCLUDE_DIR} ) diff --git a/src/server/shared/JSON/JSON.cpp b/src/server/shared/JSON/JSON.cpp new file mode 100644 index 0000000..aaac634 --- /dev/null +++ b/src/server/shared/JSON/JSON.cpp @@ -0,0 +1,22 @@ +#include "JSON.h" +#include +#include +#include +#include + +JSON JSON::FromString(std::string jsonstring) +{ + JSON json; + json.pt = new boost::property_tree::ptree(); + std::stringstream ss; + ss.str(jsonstring); + boost::property_tree::json_parser::read_json(ss, *json.pt); + return json; +} + +std::string JSON::ToString() +{ + std::stringstream ss; + boost::property_tree::json_parser::write_json(ss, *pt); + return ss.str(); +} \ No newline at end of file diff --git a/src/server/shared/JSON/JSON.h b/src/server/shared/JSON/JSON.h new file mode 100644 index 0000000..99dfe12 --- /dev/null +++ b/src/server/shared/JSON/JSON.h @@ -0,0 +1,80 @@ +#ifndef JSON_H_ +#define JSON_H_ + +#include +#include +#include +#include +#include + +class JSON +{ +public: + static JSON FromString(std::string jsonstring); + + JSON(bool base = true) : _base(base), pt(NULL) + { + if (base) + pt = new boost::property_tree::ptree(); + } + + ~JSON() + { + //if (_base && pt) + // delete pt; + } + + template + T Get(std::string key) + { + return pt->get(key); + } + + JSON operator[] (std::string key) + { + JSON json(false); + json.pt = &pt->get_child(key); + return json; + } + + JSON operator[] (int index) + { + JSON json(false); + boost::property_tree::ptree::iterator it = pt->begin(); + for (int i = 0; i < index; ++i) + ++it; + boost::property_tree::ptree::value_type& kv = *it; + json.pt = &kv.second; + return json; + } + + template + inline void Set(std::string key, T value) + { + pt->put(key, value); + } + + template + inline void Add(T value) + { + pt->push_back(std::make_pair("", value)); + } + + uint64_t Size() + { + return pt->size(); + } + + std::string ToString(); + + boost::property_tree::ptree* pt; + bool _base; +}; + +template<> +inline void JSON::Set(std::string key, JSON value) +{ + pt->put_child(key, *value.pt); +} + +#endif \ No newline at end of file diff --git a/src/server/shared/JSONRPC/JSONRPC.cpp b/src/server/shared/JSONRPC/JSONRPC.cpp new file mode 100644 index 0000000..a0174da --- /dev/null +++ b/src/server/shared/JSONRPC/JSONRPC.cpp @@ -0,0 +1,127 @@ +#include "JSONRPC.h" + +#include +#include +#include "Util.h" +#include "Log.h" + +bool JSONRPC::Connect(JSONRPCConnectionInfo connInfo) +{ + _connInfo = connInfo; + + if (_connInfo.B64Auth.empty()) + _connInfo.B64Auth = Util::ToBase64(_connInfo.User + ":" + _connInfo.Pass, false); + + sLog.Debug(LOG_JSONRPC, "JSONRPC::Connect(): B64Auth: %s", _connInfo.B64Auth.c_str()); + + boost::asio::ip::tcp::resolver resolver(_ios); + boost::asio::ip::tcp::resolver::query q(_connInfo.Host, _connInfo.Port); + boost::asio::ip::tcp::resolver::iterator epi = resolver.resolve(q); + boost::asio::ip::tcp::resolver::iterator end; + + boost::system::error_code error = boost::asio::error::host_not_found; + + while (error && epi != end) + { + _sock.close(); + _sock.connect(*epi, error); + } + + if (error) + { + sLog.Error(LOG_JSONRPC, "JSONRPC::Connect(): Error connecting to '%s': %s", _connInfo.Host.c_str(), boost::system::system_error(error).what()); + return false; + } + + _ep = *epi; + + _sock.close(); + + return true; +} + +JSON JSONRPC::Query(std::string method, JSON params) +{ + JSON request(true); + request.Set("jsonrpc", "1.0"); + request.Set("id", 1); + request.Set("method", method); + if (params.Size() > 0) + request.Set("params", params); + std::string jsonstring = request.ToString(); + + sLog.Debug(LOG_JSONRPC, "JSONRPC::Query(): JSONString: %s", jsonstring.c_str()); + + boost::system::error_code error; + + _sock.close(); + _sock.connect(_ep, error); + + if (error) + { + sLog.Error(LOG_JSONRPC, "JSONRPC::Query(): Error connecting to '%s': %s", _connInfo.Host.c_str(), boost::system::system_error(error).what()); + return NULL; + } + + boost::asio::streambuf request_buf; + std::ostream request_info(&request_buf); + request_info << "POST / HTTP/1.1\n"; + request_info << "Host: 127.0.0.1\n"; + request_info << "Content-Type: application/json-rpc\n"; + request_info << "Authorization: Basic " << _connInfo.B64Auth << "\n"; + request_info << "Content-Length: " << jsonstring.size() << "\n\n"; + request_info << jsonstring; + + boost::asio::write(_sock, request_buf); + + boost::asio::streambuf response; + boost::asio::read_until(_sock, response, "\n"); + + std::istream response_stream(&response); + + std::string http_version; + uint32_t status_code; + std::string status_message; + + response_stream >> http_version >> status_code; + std::getline(response_stream, status_message); + + + if (!response_stream || http_version.substr(0, 5) != "HTTP/") + { + sLog.Error(LOG_JSONRPC, "JSONRPC::Query(): Malformed HTTP Response"); + return NULL; + } + + if (status_code != 200) + { + sLog.Error(LOG_JSONRPC, "JSONRPC::Query(): Returned status code: %u", status_code); + return NULL; + } + + std::vector headers; + std::string header; + while (std::getline(response_stream, header) && header != "\r") + headers.push_back(header); + + std::string jsonresponse = ""; + + if (response.size() > 0) { + std::ostringstream oss; + oss << &response; + jsonresponse += oss.str(); + } + + // Read until EOF, writing data to output as we go. + while(boost::asio::read(_sock, response, boost::asio::transfer_at_least(1), error)){ + std::ostringstream oss; + oss << &response; + jsonresponse += oss.str(); + } + + _sock.close(); + + sLog.Debug(LOG_JSONRPC, "JSONRPC::Query(): JSON Response: %s", jsonresponse.c_str()); + + return JSON::FromString(jsonresponse); +} \ No newline at end of file diff --git a/src/server/shared/JSONRPC/JSONRPC.h b/src/server/shared/JSONRPC/JSONRPC.h new file mode 100644 index 0000000..7c5f3d6 --- /dev/null +++ b/src/server/shared/JSONRPC/JSONRPC.h @@ -0,0 +1,34 @@ +#ifndef JSONRPC_H_ +#define JSONRPC_H_ + +// Some of the JSON RPC code was written by https://github.com/bytemaster/cpp_bitcoin_rpc + +#include +#include +#include +#include "JSON.h" + +struct JSONRPCConnectionInfo +{ + std::string Host; + std::string Port; + std::string User; + std::string Pass; + std::string B64Auth; +}; + +class JSONRPC +{ +public: + JSONRPC(): _sock(_ios) {} + bool Connect(JSONRPCConnectionInfo conninfo); + JSON Query(std::string method, JSON params = JSON::FromString("[]")); + +private: + JSONRPCConnectionInfo _connInfo; + boost::asio::io_service _ios; + boost::asio::ip::tcp::socket _sock; + boost::asio::ip::tcp::endpoint _ep; +}; + +#endif \ No newline at end of file diff --git a/src/server/shared/Logging/Log.h b/src/server/shared/Logging/Log.h index 48e30c7..5ddaab5 100644 --- a/src/server/shared/Logging/Log.h +++ b/src/server/shared/Logging/Log.h @@ -14,18 +14,20 @@ enum LogType { - LOG_GENERAL = 0, - LOG_SERVER = 1, - LOG_DATABASE = 2 + LOG_GENERAL = 0, + LOG_SERVER = 1, + LOG_DATABASE = 2, + LOG_JSON = 3, + LOG_JSONRPC = 4 }; enum LogLevel { - LOG_LEVEL_NONE = 0, - LOG_LEVEL_ERROR = 1, - LOG_LEVEL_WARN = 2, - LOG_LEVEL_INFO = 3, - LOG_LEVEL_DEBUG = 4 + LOG_LEVEL_NONE = 0, + LOG_LEVEL_ERROR = 1, + LOG_LEVEL_WARN = 2, + LOG_LEVEL_INFO = 3, + LOG_LEVEL_DEBUG = 4 }; class Log diff --git a/src/server/shared/Util.cpp b/src/server/shared/Util.cpp index bf70584..3ab5b1c 100644 --- a/src/server/shared/Util.cpp +++ b/src/server/shared/Util.cpp @@ -1,5 +1,7 @@ #include "Util.h" +#include + std::string Util::Date(const char* format, bool utc) { boost::posix_time::ptime now; @@ -15,3 +17,28 @@ std::string Util::Date(const char* format, bool utc) return ss.str(); } + +std::string Util::ToBase64(std::string input, bool linebreaks) +{ + uint32_t writePaddChars = (3 - input.length() % 3) % 3; + + if (linebreaks) { + std::string base64(it_base64_lb_t(input.begin()), it_base64_lb_t(input.end())); + base64.append(writePaddChars, '='); + return base64; + } else { + std::string base64(it_base64_t(input.begin()), it_base64_t(input.end())); + base64.append(writePaddChars, '='); + return base64; + } +} + +std::string Util::FromBase64(std::string input) +{ + boost::algorithm::trim(input); + uint32_t paddChars = count(input.begin(), input.end(), '='); + std::replace(input.begin(), input.end(), '=', 'A'); // replace '=' by base64 encoding of '\0' + std::string result(it_binary_t(input.begin()), it_binary_t(input.end())); // decode + result.erase(result.end() - paddChars, result.end()); // erase padding '\0' characters + return result; +} diff --git a/src/server/shared/Util.h b/src/server/shared/Util.h index 71b92b6..216d476 100644 --- a/src/server/shared/Util.h +++ b/src/server/shared/Util.h @@ -4,9 +4,15 @@ #include #include #include +#include #include #include #include +#include +#include +#include +#include +#include namespace Util { @@ -73,6 +79,29 @@ namespace Util virtual void Work() = 0; boost::thread* _thread; }; + + // Base64 encode/decode functions by http://stackoverflow.com/users/1132850/piquer + + typedef boost::archive::iterators::insert_linebreaks + < + boost::archive::iterators::base64_from_binary + < + boost::archive::iterators::transform_width + >, 72 + > it_base64_lb_t; + + typedef boost::archive::iterators::base64_from_binary + < + boost::archive::iterators::transform_width + > it_base64_t; + + typedef boost::archive::iterators::transform_width + < + boost::archive::iterators::binary_from_base64, 8, 6 + > it_binary_t; + + std::string ToBase64(std::string input, bool linebreaks = true); + std::string FromBase64(std::string input); } #endif