diff --git a/.travis.yml b/.travis.yml index 519417a4..67ca6ea8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,6 +11,10 @@ before_install: - sudo make install && sudo ldconfig - cd .. && sudo rm -rf libmicrohttpd-0.9.44 - sudo pip install cpp-coveralls + - sudo add-apt-repository ppa:ubuntu-toolchain-r/test -y + - sudo apt-get update -qq + - if [ "$CXX" = "g++" ]; then sudo apt-get install -qq g++-4.8; fi + - if [ "$CXX" = "g++" ]; then export CXX="g++-4.8" CC="gcc-4.8"; fi install: - if [ "$CXX" = "g++" ]; then export CXX="g++-4.8" CC="gcc-4.8"; fi @@ -25,7 +29,6 @@ script: - mkdir -p build && cd build - cmake -DCMAKE_BUILD_TYPE=Debug -DHTTP_CLIENT=${HTTP_CLIENT} -DHTTP_SERVER=${HTTP_SERVER} -DCOMPILE_STUBGEN=${COMPILE_STUBGEN} .. - make - - valgrind --leak-check=full --error-exitcode=1 ./bin/unit_testsuite - make test - sudo make install && sudo ldconfig - g++ ../src/examples/simpleclient.cpp -ljsonrpccpp-client -ljsoncpp -ljsonrpccpp-common -lcurl -o sampleclient diff --git a/AUTHORS.md b/AUTHORS.md index 0d95cd08..081b7f70 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -31,6 +31,7 @@ Marek Kotewicz Alexandre Poirot + added client and server connectors that use Unix Domain Sockets + adapted build file to generate pkg-config file for this lib. ++ added client and server connectors that use Tcp Sockets on Linux and Windows (uses native socket and thread API on each OS) Bugfixes (chronological order) ============================== diff --git a/CHANGELOG.md b/CHANGELOG.md index 6358a9a8..d3204857 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,17 @@ +Changes in v0.7.0 +----------------- +- Change: Requiring C++11 support (gcc >= 4.8) +- Fix: armhf compatibility +- Fix: Invalid client id field handling (removed int only check) +- Fix: Security issues in unixdomainsocket connectors +- Fix: Missing CURL include directive +- Fix: Parallel build which failed due to failing CATCH dependency +- Fix: Handling 64-bit ids +- Fix: Invalid parameter check +- Fix: Invalid pointer handling in HTTP-Server +- NEW: HttpServer can now be configured to listen localhost only +- NEW: TCP Server + Client connectors + Changes in v0.6.0 ----------------- - NEW: pkg-config files for all shared libraries diff --git a/CMakeLists.txt b/CMakeLists.txt index 9c5b037c..a3cf5e0a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,7 +11,7 @@ if (${CMAKE_MAJOR_VERSION} GREATER 2) endif() set(MAJOR_VERSION 0) -set(MINOR_VERSION 6) +set(MINOR_VERSION 7) set(PATCH_VERSION 0) set(SO_VERSION 0) @@ -25,9 +25,11 @@ endif() # defaults for modules that can be enabled/disabled if(UNIX) - set(UNIX_DOMAIN_SOCKET_SERVER YES CACHE BOOL "Include Unix Domain Socket server") - set(UNIX_DOMAIN_SOCKET_CLIENT YES CACHE BOOL "Include Unix Domain Socket client") + set(UNIX_DOMAIN_SOCKET_SERVER YES CACHE BOOL "Include Unix Domain Socket server") + set(UNIX_DOMAIN_SOCKET_CLIENT YES CACHE BOOL "Include Unix Domain Socket client") endif(UNIX) +set(TCP_SOCKET_SERVER NO CACHE BOOL "Include Tcp Socket server") +set(TCP_SOCKET_CLIENT NO CACHE BOOL "Include Tcp Socket client") set(HTTP_SERVER YES CACHE BOOL "Include HTTP server using libmicrohttpd") set(HTTP_CLIENT YES CACHE BOOL "Include HTTP client support using curl") set(COMPILE_TESTS YES CACHE BOOL "Compile test framework") @@ -35,6 +37,12 @@ set(COMPILE_STUBGEN YES CACHE BOOL "Compile the stubgenerator") set(COMPILE_EXAMPLES YES CACHE BOOL "Compile example programs") # print actual settings +if(UNIX) + message(STATUS "UNIX_DOMAIN_SOCKET_SERVER: ${UNIX_DOMAIN_SOCKET_SERVER}") + message(STATUS "UNIX_DOMAIN_SOCKET_CLIENT: ${UNIX_DOMAIN_SOCKET_CLIENT}") +endif(UNIX) +message(STATUS "TCP_SOCKET_SERVER: ${TCP_SOCKET_SERVER}") +message(STATUS "TCP_SOCKET_CLIENT: ${TCP_SOCKET_CLIENT}") message(STATUS "HTTP_SERVER: ${HTTP_SERVER}") message(STATUS "HTTP_CLIENT: ${HTTP_CLIENT}") if(UNIX) diff --git a/README.md b/README.md index 4c24326f..08fdd5b6 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ It is fully JSON-RPC [2.0 & 1.0 compatible](http://www.jsonrpc.org/specification **5 good reasons for using libjson-rpc-cpp in your next RPC project** - Full JSON-RPC 2.0 & 1.0 Client and Server Support. - jsonrpcstub - a tool that generates stub-classes for your JSON-RPC client AND server applications. -- Ready to use HTTP server and client to provide simple interfaces for your JSON-RPC application. +- Ready to use HTTP + TCP server and client to provide simple interfaces for your JSON-RPC application. - Cross platform build support and [precompiled binaries for WIN32](http://spiessknafl.at/libjson-rpc-cpp). - Super liberal [MIT-License](http://en.wikipedia.org/wiki/MIT_License). @@ -111,6 +111,8 @@ Default configuration should be fine for most systems, but here are available co - `-DHTTP_CLIENT=NO` disable the curl client. - `-DUNIX_DOMAIN_SOCKET_SERVER=NO` disable the unix domain socket server connector. - `-DUNIX_DOMAIN_SOCKET_CLIENT=NO` disable the unix domain socket client connector. +- `-DTCP_SOCKET_SERVER=NO` disable the tcp socket server connector. +- `-DTCP_SOCKET_CLIENT=NO` disable the tcp socket client connector. Using the framework =================== diff --git a/cmake/CMakeCompilerSettings.cmake b/cmake/CMakeCompilerSettings.cmake index 8fbc45af..cca7ebec 100644 --- a/cmake/CMakeCompilerSettings.cmake +++ b/cmake/CMakeCompilerSettings.cmake @@ -3,7 +3,7 @@ if ("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") - set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Wall -Wextra -Wnon-virtual-dtor -fprofile-arcs -ftest-coverage -fPIC -O0") + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Wall -Wformat -Wno-format-extra-args -Wformat-security -Wformat-nonliteral -Wformat=2 -Wextra -Wnon-virtual-dtor -fprofile-arcs -ftest-coverage -fPIC -O0") elseif ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang") # TODO figure clang stuff to enable test-coverage # Instrument Program flow should be set to Yes diff --git a/dev/ci.sh b/dev/ci.sh index aa967a6e..5b6b1f44 100755 --- a/dev/ci.sh +++ b/dev/ci.sh @@ -7,7 +7,6 @@ build_configuration() { cd build cmake $1 .. make - make test mkdir -p root make DESTDIR=root install cd .. @@ -37,11 +36,11 @@ valgrind --leak-check=full --xml=yes --xml-file=../reports/valgrind.xml ./bin/un echo "Generating coverage report" -gcovr -x -r .. > ../reports/coverage.xml -gcovr -r .. --html --html-details -o ../reports/coverage.html +gcovr -e "build" -e "src/test" -x -r .. > ../reports/coverage.xml +gcovr -e "build" -e "src/test" -r .. --html --html-details -o ../reports/coverage.html echo "Generating cppcheck report" -cppcheck --enable=all --xml ../src --xml-version=2 2> ../reports/cppcheck.xml +cppcheck -I ../src --enable=all --xml ../src --xml-version=2 2> ../reports/cppcheck.xml cd .. echo "Cleanup that mess" diff --git a/src/examples/CMakeLists.txt b/src/examples/CMakeLists.txt index 779f42a1..e20bd0a2 100644 --- a/src/examples/CMakeLists.txt +++ b/src/examples/CMakeLists.txt @@ -36,6 +36,24 @@ include_directories(${CMAKE_BINARY_DIR}) include_directories(${JSONCPP_INCLUDE_DIRS}) include_directories(${MHD_INCLUDE_DIRS}) +if(UNIX) + if (UNIX_DOMAIN_SOCKET_SERVER AND UNIX_DOMAIN_SOCKET_CLIENT) + add_executable(unixdomainsocketserversample unixdomainsocketserver.cpp) + target_link_libraries(unixdomainsocketserversample jsonrpcserver) + + add_executable(unixdomainsocketclientsample unixdomainsocketclient.cpp) + target_link_libraries(unixdomainsocketclientsample jsonrpcclient) + endif (UNIX_DOMAIN_SOCKET_SERVER AND UNIX_DOMAIN_SOCKET_CLIENT) +endif(UNIX) + +if (TCP_SOCKET_SERVER AND TCP_SOCKET_CLIENT) + add_executable(tcpsocketclient tcpsocketclient.cpp) + target_link_libraries(tcpsocketclient jsonrpcclient) + + add_executable(tcpsocketserver tcpsocketserver.cpp) + target_link_libraries(tcpsocketserver jsonrpcserver) +endif (TCP_SOCKET_SERVER AND TCP_SOCKET_CLIENT) + if(HTTP_SERVER) add_executable(simpleserversample simpleserver.cpp) target_link_libraries(simpleserversample jsonrpcserver) @@ -59,3 +77,4 @@ if (COMPILE_STUBGEN) target_link_libraries(stubserversample jsonrpcserver) endif() endif() + diff --git a/src/examples/stubclient.cpp b/src/examples/stubclient.cpp index e0a99b09..3b3d8151 100644 --- a/src/examples/stubclient.cpp +++ b/src/examples/stubclient.cpp @@ -17,6 +17,26 @@ using namespace std; int main() { + + Json::Value a = 3; + Json::Value b = "3"; + + std::map responses; + + responses[a] = b; + responses[b] = "asölfj"; + + cout << responses[b] << endl; + + if (a == b) + { + cout << a.toStyledString() << " == " << b.toStyledString() << endl; + } + else + { + cout << a.toStyledString() << " != " << b.toStyledString() << endl; + } + HttpClient httpclient("http://localhost:8383"); //StubClient c(httpclient, JSONRPC_CLIENT_V1); //json-rpc 1.0 StubClient c(httpclient, JSONRPC_CLIENT_V2); //json-rpc 2.0 diff --git a/src/examples/tcpsocketclient.cpp b/src/examples/tcpsocketclient.cpp new file mode 100644 index 00000000..98a4a3e0 --- /dev/null +++ b/src/examples/tcpsocketclient.cpp @@ -0,0 +1,51 @@ +/** + * @file tcpsocketclient.cpp + * @date 17.07.2015 + * @author Alexandre Poirot + * @brief tcpsocketclient.cpp + */ + +#include +#include +#include +#include + +using namespace jsonrpc; +using namespace std; + +int main(int argc, char** argv) +{ + string host; + unsigned int port; + + if(argc == 3) { + host = string(argv[1]); + port = atoi(argv[2]); + } + else { + host = "127.0.0.1"; + port = 6543; + } + + + cout << "Params are :" << endl; + cout << "\t host: " << host << endl; + cout << "\t port: " << port << endl; + + TcpSocketClient client(host, port); + Client c(client); + + Json::Value params; + params["name"] = "Peter"; + + try + { + cout << c.CallMethod("sayHello", params) << endl; + } + catch (JsonRpcException& e) + { + cerr << e.what() << endl; + } + + +} diff --git a/src/examples/tcpsocketserver.cpp b/src/examples/tcpsocketserver.cpp new file mode 100644 index 00000000..25d1d54c --- /dev/null +++ b/src/examples/tcpsocketserver.cpp @@ -0,0 +1,82 @@ +/** + * @file unixdomainsocketserver.cpp + * @date 11.05.2015 + * @author Alexandre Poirot + * @brief unixdomainsocketserver.cpp + */ + +#include +#include +#include +#include +#include +#include + + +using namespace jsonrpc; +using namespace std; + +class SampleServer : public AbstractServer +{ + public: + SampleServer(TcpSocketServer &server) : + AbstractServer(server) + { + this->bindAndAddMethod(Procedure("sayHello", PARAMS_BY_NAME, JSON_STRING, "name", JSON_STRING, NULL), &SampleServer::sayHello); + this->bindAndAddNotification(Procedure("notifyServer", PARAMS_BY_NAME, NULL), &SampleServer::notifyServer); + } + + //method + void sayHello(const Json::Value& request, Json::Value& response) + { + response = "Hello: " + request["name"].asString(); + } + + //notification + void notifyServer(const Json::Value& request) + { + (void)request; + cout << "server received some Notification" << endl; + } +}; + +int main(int argc, char** argv) +{ + try + { + string ip; + unsigned int port; + + if(argc == 3) + { + ip = string(argv[1]); + port = atoi(argv[2]); + } + else + { + ip = "127.0.0.1"; + port = 6543; + } + + cout << "Params are :" << endl; + cout << "\t ip: " << ip << endl; + cout << "\t port: " << port << endl; + + TcpSocketServer server(ip, port); + SampleServer serv(server); + if (serv.StartListening()) + { + cout << "Server started successfully" << endl; + getchar(); + serv.StopListening(); + } + else + { + cout << "Error starting Server" << endl; + } + } + catch (jsonrpc::JsonRpcException& e) + { + cerr << e.what() << endl; + } +} diff --git a/src/examples/unixdomainsocketclient.cpp b/src/examples/unixdomainsocketclient.cpp new file mode 100644 index 00000000..80ac0f40 --- /dev/null +++ b/src/examples/unixdomainsocketclient.cpp @@ -0,0 +1,33 @@ +/** + * @file unixdomainsocketclient.cpp + * @date 11.05.2015 + * @author Alexandre Poirot + * @brief unixdomainsocketclient.cpp + */ + +#include +#include +#include + +using namespace jsonrpc; +using namespace std; + +int main() +{ + UnixDomainSocketClient client("/tmp/unixdomainsocketexample"); + Client c(client); + + Json::Value params; + params["name"] = "Peter"; + + try + { + cout << c.CallMethod("sayHello", params) << endl; + } + catch (JsonRpcException& e) + { + cerr << e.what() << endl; + } + + +} diff --git a/src/examples/unixdomainsocketserver.cpp b/src/examples/unixdomainsocketserver.cpp new file mode 100644 index 00000000..04d694ab --- /dev/null +++ b/src/examples/unixdomainsocketserver.cpp @@ -0,0 +1,63 @@ +/** + * @file unixdomainsocketserver.cpp + * @date 11.05.2015 + * @author Alexandre Poirot + * @brief unixdomainsocketserver.cpp + */ + +#include +#include +#include +#include +#include + + +using namespace jsonrpc; +using namespace std; + +class SampleServer : public AbstractServer +{ + public: + SampleServer(UnixDomainSocketServer &server) : + AbstractServer(server) + { + this->bindAndAddMethod(Procedure("sayHello", PARAMS_BY_NAME, JSON_STRING, "name", JSON_STRING, NULL), &SampleServer::sayHello); + this->bindAndAddNotification(Procedure("notifyServer", PARAMS_BY_NAME, NULL), &SampleServer::notifyServer); + } + + //method + void sayHello(const Json::Value& request, Json::Value& response) + { + response = "Hello: " + request["name"].asString(); + } + + //notification + void notifyServer(const Json::Value& request) + { + (void)request; + cout << "server received some Notification" << endl; + } +}; + +int main() +{ + try + { + UnixDomainSocketServer server("/tmp/unixdomainsocketexample"); + SampleServer serv(server); + if (serv.StartListening()) + { + cout << "Server started successfully" << endl; + getchar(); + serv.StopListening(); + } + else + { + cout << "Error starting Server" << endl; + } + } + catch (jsonrpc::JsonRpcException& e) + { + cerr << e.what() << endl; + } +} diff --git a/src/jsonrpccpp/CMakeLists.txt b/src/jsonrpccpp/CMakeLists.txt index d22a2e1f..d82cc9aa 100644 --- a/src/jsonrpccpp/CMakeLists.txt +++ b/src/jsonrpccpp/CMakeLists.txt @@ -37,6 +37,7 @@ if (HTTP_CLIENT) list(APPEND client_connector_header "client/connectors/httpclient.h") list(APPEND client_connector_source "client/connectors/httpclient.cpp") list(APPEND client_connector_libs ${CURL_LIBRARIES}) + include_directories(${CURL_INCLUDE_DIRS}) endif() if (HTTP_SERVER) @@ -47,14 +48,45 @@ endif() # setup sources for unix domain socket connectors if (UNIX_DOMAIN_SOCKET_SERVER) - list(APPEND server_connector_header "server/connectors/unixdomainsocketserver.h") - list(APPEND server_connector_source "server/connectors/unixdomainsocketserver.cpp") - list(APPEND server_connector_libs ${CMAKE_THREAD_LIBS_INIT}) + list(APPEND server_connector_header "server/connectors/unixdomainsocketserver.h") + list(APPEND server_connector_source "server/connectors/unixdomainsocketserver.cpp") + list(APPEND server_connector_libs ${CMAKE_THREAD_LIBS_INIT}) endif() if (UNIX_DOMAIN_SOCKET_CLIENT) - list(APPEND client_connector_header "client/connectors/unixdomainsocketclient.h") - list(APPEND client_connector_source "client/connectors/unixdomainsocketclient.cpp") + list(APPEND client_connector_header "client/connectors/unixdomainsocketclient.h") + list(APPEND client_connector_source "client/connectors/unixdomainsocketclient.cpp") +endif() + +# setup sources for tcp socket connectors +if (TCP_SOCKET_SERVER) + list(APPEND server_connector_header "server/connectors/tcpsocketserver.h") + list(APPEND server_connector_source "server/connectors/tcpsocketserver.cpp") + if (WIN32) + list(APPEND server_connector_header "server/connectors/windowstcpsocketserver.h") + list(APPEND server_connector_source "server/connectors/windowstcpsocketserver.cpp") + list(APPEND server_connector_libs ws2_32) + endif() + if(UNIX) + list(APPEND server_connector_header "server/connectors/linuxtcpsocketserver.h") + list(APPEND server_connector_source "server/connectors/linuxtcpsocketserver.cpp") + endif() + list(APPEND server_connector_libs ${CMAKE_THREAD_LIBS_INIT}) +endif() + +if (TCP_SOCKET_CLIENT) + list(APPEND client_connector_header "client/connectors/tcpsocketclient.h") + list(APPEND client_connector_source "client/connectors/tcpsocketclient.cpp") + if (WIN32) + list(APPEND client_connector_header "client/connectors/windowstcpsocketclient.h") + list(APPEND client_connector_source "client/connectors/windowstcpsocketclient.cpp") + list(APPEND client_connector_libs ws2_32) + endif() + if(UNIX) + list(APPEND client_connector_header "client/connectors/linuxtcpsocketclient.h") + list(APPEND client_connector_source "client/connectors/linuxtcpsocketclient.cpp") + endif() + list(APPEND client_connector_libs ${CMAKE_THREAD_LIBS_INIT}) endif() # configure a header file to pass some of the CMake settings to the source code @@ -161,8 +193,8 @@ if (WIN32) endif() install(TARGETS ${ALL_LIBS} - LIBRARY DESTINATION lib/${CMAKE_LIBRARY_PATH} - ARCHIVE DESTINATION lib/${CMAKE_LIBRARY_PATH} + LIBRARY DESTINATION lib${LIB_SUFFIX}/${CMAKE_LIBRARY_PATH} + ARCHIVE DESTINATION lib${LIB_SUFFIX}/${CMAKE_LIBRARY_PATH} RUNTIME DESTINATION bin ) @@ -179,6 +211,6 @@ INSTALL(FILES "${CMAKE_BINARY_DIR}/libjsonrpccpp-server.pc" "${CMAKE_BINARY_DIR}/libjsonrpccpp-client.pc" "${CMAKE_BINARY_DIR}/libjsonrpccpp-common.pc" - DESTINATION "lib/${CMAKE_LIBRARY_PATH}/pkgconfig") + DESTINATION "lib${LIB_SUFFIX}/${CMAKE_LIBRARY_PATH}/pkgconfig") diff --git a/src/jsonrpccpp/client/batchresponse.cpp b/src/jsonrpccpp/client/batchresponse.cpp index 8628fd88..6e59d78f 100644 --- a/src/jsonrpccpp/client/batchresponse.cpp +++ b/src/jsonrpccpp/client/batchresponse.cpp @@ -18,7 +18,7 @@ BatchResponse::BatchResponse() { } -void BatchResponse::addResponse(int id, Json::Value response, bool isError) +void BatchResponse::addResponse(Json::Value &id, Json::Value response, bool isError) { if (isError) { errorResponses.push_back(id); @@ -26,14 +26,22 @@ void BatchResponse::addResponse(int id, Json::Value response, bool isError) responses[id] = response; } -Json::Value BatchResponse::getResult(int id) +Json::Value BatchResponse::getResult(Json::Value& id) { Json::Value result; getResult(id, result); return result; } -void BatchResponse::getResult(int id, Json::Value &result) +Json::Value BatchResponse::getResult(int id) +{ + Json::Value result; + Json::Value i = id; + getResult(i, result); + return result; +} + +void BatchResponse::getResult(Json::Value& id, Json::Value &result) { if (getErrorCode(id) == 0) result = responses[id]; @@ -41,7 +49,7 @@ void BatchResponse::getResult(int id, Json::Value &result) result = Json::nullValue; } -int BatchResponse::getErrorCode(int id) +int BatchResponse::getErrorCode(Json::Value& id) { if(std::find(errorResponses.begin(), errorResponses.end(), id) != errorResponses.end()) { @@ -50,7 +58,7 @@ int BatchResponse::getErrorCode(int id) return 0; } -string BatchResponse::getErrorMessage(int id) +string BatchResponse::getErrorMessage(Json::Value& id) { if(std::find(errorResponses.begin(), errorResponses.end(), id) != errorResponses.end()) { @@ -59,6 +67,12 @@ string BatchResponse::getErrorMessage(int id) return ""; } +string BatchResponse::getErrorMessage(int id) +{ + Json::Value i = id; + return getErrorMessage(i); +} + bool BatchResponse::hasErrors() { return !errorResponses.empty(); diff --git a/src/jsonrpccpp/client/batchresponse.h b/src/jsonrpccpp/client/batchresponse.h index 6480cce0..fa44be9a 100644 --- a/src/jsonrpccpp/client/batchresponse.h +++ b/src/jsonrpccpp/client/batchresponse.h @@ -29,7 +29,7 @@ namespace jsonrpc { * @param response * @param isError */ - void addResponse(int id, Json::Value response, bool isError = false); + void addResponse(Json::Value& id, Json::Value response, bool isError = false); /** * @brief getResult method gets the result for a given request id (returned by BatchCall::addCall. @@ -37,29 +37,33 @@ namespace jsonrpc { * @param id * @return */ + Json::Value getResult(Json::Value& id); + Json::Value getResult(int id); - void getResult(int id, Json::Value &result); + void getResult(Json::Value& id, Json::Value &result); /** * @brief getErrorCode method checks if for a given id, an error occurred in the batch request. * @param id */ - int getErrorCode(int id); + int getErrorCode(Json::Value& id); /** * @brief getErrorMessage method gets the corresponding error message. * @param id * @return the error message in case of an error, an empty string if no error was found for the provided id. */ + std::string getErrorMessage(Json::Value& id); + std::string getErrorMessage(int id); bool hasErrors(); private: - std::map responses; - std::vector errorResponses; + std::map responses; + std::vector errorResponses; }; diff --git a/src/jsonrpccpp/client/client.cpp b/src/jsonrpccpp/client/client.cpp index a43b92ed..7ce96587 100644 --- a/src/jsonrpccpp/client/client.cpp +++ b/src/jsonrpccpp/client/client.cpp @@ -49,13 +49,13 @@ void Client::CallProcedures(const BatchCall &calls, BatchResponse &result) throw if (tmpresult[i].isObject()) { Json::Value singleResult; try { - int id = this->protocol->HandleResponse(tmpresult[i], singleResult); + Json::Value id = this->protocol->HandleResponse(tmpresult[i], singleResult); result.addResponse(id, singleResult, false); } catch (JsonRpcException& ex) { - int id = -1; - if(tmpresult[i].isMember("id") && tmpresult[i]["id"].isIntegral()) - id = tmpresult[i]["id"].asInt(); + Json::Value id = -1; + if(tmpresult[i].isMember("id")) + id = tmpresult[i]["id"]; result.addResponse(id, tmpresult[i]["error"], true); } } diff --git a/src/jsonrpccpp/client/connectors/linuxtcpsocketclient.cpp b/src/jsonrpccpp/client/connectors/linuxtcpsocketclient.cpp new file mode 100644 index 00000000..631998dd --- /dev/null +++ b/src/jsonrpccpp/client/connectors/linuxtcpsocketclient.cpp @@ -0,0 +1,237 @@ +/************************************************************************* + * libjson-rpc-cpp + ************************************************************************* + * @file linuxtcpsocketclient.cpp + * @date 17.07.2015 + * @author Alexandre Poirot + * @license See attached LICENSE.txt + ************************************************************************/ + +#include "linuxtcpsocketclient.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define BUFFER_SIZE 64 +#ifndef DELIMITER_CHAR +#define DELIMITER_CHAR char(0x0A) +#endif //DELIMITER_CHAR + +using namespace jsonrpc; +using namespace std; + +LinuxTcpSocketClient::LinuxTcpSocketClient(const std::string& hostToConnect, const unsigned int &port) : + TcpSocketClientPrivate(), + hostToConnect(hostToConnect), + port(port) +{ +} + +LinuxTcpSocketClient::~LinuxTcpSocketClient() +{ +} + +void LinuxTcpSocketClient::SendRPCMessage(const std::string& message, std::string& result) throw (JsonRpcException) +{ + int socket_fd = this->Connect(); + char buffer[BUFFER_SIZE]; + + bool fullyWritten = false; + string toSend = message; + do + { + ssize_t byteWritten = send(socket_fd, toSend.c_str(), toSend.size(), 0); + if(byteWritten == -1) + { + string message = "send() failed"; + int err = errno; + switch(err) + { + case EACCES: + case EWOULDBLOCK: + case EBADF: + case ECONNRESET: + case EDESTADDRREQ: + case EFAULT: + case EINTR: + case EINVAL: + case EISCONN: + case EMSGSIZE: + case ENOBUFS: + case ENOMEM: + case ENOTCONN: + case ENOTSOCK: + case EOPNOTSUPP: + case EPIPE: + message = strerror(err); + break; + } + close(socket_fd); + throw JsonRpcException(Errors::ERROR_CLIENT_CONNECTOR, message); + } + else if(static_cast(byteWritten) < toSend.size()) + { + int len = toSend.size() - byteWritten; + toSend = toSend.substr(byteWritten + sizeof(char), len); + } + else + fullyWritten = true; + } while(!fullyWritten); + + do + { + int nbytes = recv(socket_fd, buffer, BUFFER_SIZE, 0); + if(nbytes == -1) + { + string message = "recv() failed"; + int err = errno; + switch(err) + { + case EWOULDBLOCK: + case EBADF: + case ECONNRESET: + case EFAULT: + case EINTR: + case EINVAL: + case ENOMEM: + case ENOTCONN: + case ENOTSOCK: + message = strerror(err); + break; + } + close(socket_fd); + throw JsonRpcException(Errors::ERROR_CLIENT_CONNECTOR, message); + } + else + { + string tmp; + tmp.append(buffer, nbytes); + result.append(buffer,nbytes); + } + } while(result.find(DELIMITER_CHAR) == string::npos); + + close(socket_fd); +} + +int LinuxTcpSocketClient::Connect() throw (JsonRpcException) +{ + if(this->IsIpv4Address(this->hostToConnect)) + { + return this->Connect(this->hostToConnect, this->port); + } + else //We were given a hostname + { + struct addrinfo *result = NULL; + struct addrinfo hints; + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + char port[6]; + snprintf(port, 6, "%d", this->port); + int retval = getaddrinfo(this->hostToConnect.c_str(), port, &hints, &result); + if(retval != 0) + throw JsonRpcException(Errors::ERROR_CLIENT_CONNECTOR, "Could not resolve hostname."); + bool foundValidIp = false; + int socket_fd; + for(struct addrinfo *temp = result; (temp != NULL) && !foundValidIp; temp = temp->ai_next) + { + if(temp->ai_family == AF_INET) + { + try + { + sockaddr_in* sock = reinterpret_cast(temp->ai_addr); + socket_fd = this->Connect(inet_ntoa(sock->sin_addr), ntohs(sock->sin_port)); + foundValidIp = true; + } + catch(const JsonRpcException& e) + { + foundValidIp = false; + socket_fd = -1; + } + catch(void* p) + { + foundValidIp = false; + socket_fd = -1; + } + } + } + + if(!foundValidIp) + throw JsonRpcException(Errors::ERROR_CLIENT_CONNECTOR, "Hostname resolved but connection was refused on the given port."); + + return socket_fd; + } +} + +int LinuxTcpSocketClient::Connect(const string& ip, const int& port) throw (JsonRpcException) +{ + sockaddr_in address; + int socket_fd; + socket_fd = socket(AF_INET, SOCK_STREAM, 0); + if (socket_fd < 0) + { + string message = "socket() failed"; + int err = errno; + switch(err) + { + case EACCES: + case EAFNOSUPPORT: + case EINVAL: + case EMFILE: + case ENOBUFS: + case ENOMEM: + case EPROTONOSUPPORT: + message = strerror(err); + break; + } + throw JsonRpcException(Errors::ERROR_CLIENT_CONNECTOR, message); + } + memset(&address, 0, sizeof(sockaddr_in)); + + address.sin_family = AF_INET; + inet_aton(ip.c_str(), &(address.sin_addr)); + address.sin_port = htons(port); + + if(connect(socket_fd, (struct sockaddr *) &address, sizeof(sockaddr_in)) != 0) + { + string message = "connect() failed"; + int err = errno; + switch(err) + { + case EACCES: + case EPERM: + case EADDRINUSE: + case EAFNOSUPPORT: + case EAGAIN: + case EALREADY: + case EBADF: + case ECONNREFUSED: + case EFAULT: + case EINPROGRESS: + case EINTR: + case EISCONN: + case ENETUNREACH: + case ENOTSOCK: + case ETIMEDOUT: + message = strerror(err); + break; + } + throw JsonRpcException(Errors::ERROR_CLIENT_CONNECTOR, message); + } + return socket_fd; +} + +bool LinuxTcpSocketClient::IsIpv4Address(const std::string& ip) +{ + struct in_addr addr; + return (inet_aton(ip.c_str(), &addr) != 0); +} diff --git a/src/jsonrpccpp/client/connectors/linuxtcpsocketclient.h b/src/jsonrpccpp/client/connectors/linuxtcpsocketclient.h new file mode 100644 index 00000000..5d05315f --- /dev/null +++ b/src/jsonrpccpp/client/connectors/linuxtcpsocketclient.h @@ -0,0 +1,79 @@ +/************************************************************************* + * libjson-rpc-cpp + ************************************************************************* + * @file linuxtcpsocketclient.h + * @date 17.07.2015 + * @author Alexandre Poirot + * @license See attached LICENSE.txt + ************************************************************************/ + +#ifndef JSONRPC_CPP_LINUXTCPSOCKETCLIENT_H_ +#define JSONRPC_CPP_LINUXTCPSOCKETCLIENT_H_ + +#include +#include +#include "tcpsocketclientprivate.h" + +namespace jsonrpc +{ + /** + * This class is the Linux/UNIX implementation of TCPSocketClient. + * It uses the POSIX socket API to performs its job. + */ + class LinuxTcpSocketClient : public TcpSocketClientPrivate + { + public: + /** + * @brief LinuxTcpSocketClient, constructor of the Linux/UNIX implementation of class TcpSocketClient + * @param hostToConnect The hostname or the ipv4 address on which the client should try to connect + * @param port The port on which the client should try to connect + */ + LinuxTcpSocketClient(const std::string& hostToConnect, const unsigned int &port); + /** + * @brief ~LinuxTcpSocketClient, the destructor of LinuxTcpSocketClient + */ + virtual ~LinuxTcpSocketClient(); + /** + * @brief The real implementation of TcpSocketClient::SendRPCMessage method. + * @param message The message to send + * @param result The result of the call returned by the server + * @throw JsonRpcException Thrown when an issue is encountered with socket manipulation (see message of exception for more information about what happened). + */ + virtual void SendRPCMessage(const std::string& message, std::string& result) throw (JsonRpcException); + + private: + std::string hostToConnect; /*!< The hostname or the ipv4 address on which the client should try to connect*/ + unsigned int port; /*!< The port on which the client should try to connect*/ + /** + * @brief Connects to the host and port provided by constructor parameters. + * + * This method detects if the hostToConnect attribute is either an IPv4 or a hostname. + * On first case it tries to connect to the ip. + * On second case it tries to resolve hostname to an ip and tries to connect to it if resolve was successful. + * + * @returns A file descriptor to the successfully connected socket + * @throw JsonRpcException Thrown when an issue is encountered while trying to connect (see message of exception for more information about what happened). + */ + int Connect() throw (JsonRpcException); + /** + * @brief Connects to provided ip and port. + * + * This method tries to connect to the provided ip and port. + * + * @param ip The ipv4 address to connect to + * @param port The port to connect to + * @returns A file descriptor to the successfully connected socket + * @throw JsonRpcException Thrown when an issue is encountered while trying to connect (see message of exception for more information about what happened). + */ + int Connect(const std::string& ip, const int& port) throw (JsonRpcException); + /** + * @brief Check if provided ip is an ipv4 address. + * + * @param ip The ipv4 address to check + * @returns A boolean indicating if the provided ip is or is not an ipv4 address + */ + bool IsIpv4Address(const std::string& ip); + }; + +} /* namespace jsonrpc */ +#endif /* JSONRPC_CPP_LINUXTCPSOCKETCLIENT_H_ */ diff --git a/src/jsonrpccpp/client/connectors/tcpsocketclient.cpp b/src/jsonrpccpp/client/connectors/tcpsocketclient.cpp new file mode 100644 index 00000000..874e0faa --- /dev/null +++ b/src/jsonrpccpp/client/connectors/tcpsocketclient.cpp @@ -0,0 +1,47 @@ +/************************************************************************* + * libjson-rpc-cpp + ************************************************************************* + * @file tcpsocketclient.cpp + * @date 17.07.2015 + * @author Alexandre Poirot + * @license See attached LICENSE.txt + ************************************************************************/ + +#include "tcpsocketclient.h" + +#ifdef __WIN32__ +#include "windowstcpsocketclient.h" +#elif __unix__ +#include "linuxtcpsocketclient.h" +#endif + +using namespace jsonrpc; +using namespace std; + +TcpSocketClient::TcpSocketClient(const std::string& ipToConnect, const unsigned int &port) +{ +#ifdef __WIN32__ + this->realSocket = new WindowsTcpSocketClient(ipToConnect, port); +#elif __unix__ + this->realSocket = new LinuxTcpSocketClient(ipToConnect, port); +#else + this->realSocket = NULL; +#endif +} + +TcpSocketClient::~TcpSocketClient() +{ + if(this->realSocket != NULL) + { + delete this->realSocket; + this->realSocket = NULL; + } +} + +void TcpSocketClient::SendRPCMessage(const std::string& message, std::string& result) throw (JsonRpcException) +{ + if(this->realSocket != NULL) + { + this->realSocket->SendRPCMessage(message, result); + } +} diff --git a/src/jsonrpccpp/client/connectors/tcpsocketclient.h b/src/jsonrpccpp/client/connectors/tcpsocketclient.h new file mode 100644 index 00000000..66e80054 --- /dev/null +++ b/src/jsonrpccpp/client/connectors/tcpsocketclient.h @@ -0,0 +1,56 @@ +/************************************************************************* + * libjson-rpc-cpp + ************************************************************************* + * @file unixdomainsocketclient.h + * @date 11.05.2015 + * @author Alexandre Poirot + * @license See attached LICENSE.txt + ************************************************************************/ + +#ifndef JSONRPC_CPP_TCPSOCKETCLIENT_H_ +#define JSONRPC_CPP_TCPSOCKETCLIENT_H_ + +#include "../iclientconnector.h" +#include +#include +#include "tcpsocketclientprivate.h" + +namespace jsonrpc +{ + /** + * This class provides an embedded TCP Socket Client that sends Requests over a tcp socket and read result from same socket. + * It uses the delimiter character to distinct a full RPC Message over the tcp flow. This character is parametered on + * compilation time in implementation files. The default value for this delimiter is 0x0A a.k.a. "new line". + * This class hides OS specific features in real implementation of this client. Currently it has implementation for + * both Linux and Windows. + */ + class TcpSocketClient : public IClientConnector + { + public: + /** + * @brief TcpSocketClient, constructor for the included TcpSocketClient + * + * Instanciates the real implementation of TcpSocketClientPrivate depending on running OS. + * + * @param ipToConnect The ipv4 address on which the client should try to connect + * @param port The port on which the client should try to connect + */ + TcpSocketClient(const std::string& ipToConnect, const unsigned int &port); + /** + * @brief ~TcpSocketClient, the destructor of TcpSocketClient + */ + virtual ~TcpSocketClient(); + /** + * @brief The IClientConnector::SendRPCMessage method overload. + * @param message The message to send + * @param result The result of the call returned by the servsr + * @throw JsonRpcException Thrown when an issue is encounter with socket manipulation (see message of exception for more information about what happened). + */ + virtual void SendRPCMessage(const std::string& message, std::string& result) throw (JsonRpcException); + + private: + TcpSocketClientPrivate *realSocket; /*!< A pointer to the real implementation of this class depending of running OS*/ + }; + +} /* namespace jsonrpc */ +#endif /* JSONRPC_CPP_TCPSOCKETCLIENT_H_ */ diff --git a/src/jsonrpccpp/client/connectors/tcpsocketclientprivate.h b/src/jsonrpccpp/client/connectors/tcpsocketclientprivate.h new file mode 100644 index 00000000..767eecfe --- /dev/null +++ b/src/jsonrpccpp/client/connectors/tcpsocketclientprivate.h @@ -0,0 +1,26 @@ +/************************************************************************* + * libjson-rpc-cpp + ************************************************************************* + * @file tcpsocketclientprivate.h + * @date 17.07.2015 + * @author Alexandre Poirot + * @license See attached LICENSE.txt + ************************************************************************/ + +#ifndef JSONRPC_CPP_TCPSOCKETCLIENTPRIVATE_H_ +#define JSONRPC_CPP_TCPSOCKETCLIENTPRIVATE_H_ + +#include "../iclientconnector.h" +#include + +namespace jsonrpc +{ + /** + * This class is an interface to the real implementation of TcpSocketClient. Kind of a strategy design pattern. + */ + class TcpSocketClientPrivate : public IClientConnector + { + }; + +} /* namespace jsonrpc */ +#endif /* JSONRPC_CPP_TCPSOCKETCLIENTPRIVATE_H_ */ diff --git a/src/jsonrpccpp/client/connectors/unixdomainsocketclient.cpp b/src/jsonrpccpp/client/connectors/unixdomainsocketclient.cpp index 4e923e3b..585e88d8 100644 --- a/src/jsonrpccpp/client/connectors/unixdomainsocketclient.cpp +++ b/src/jsonrpccpp/client/connectors/unixdomainsocketclient.cpp @@ -27,54 +27,58 @@ using namespace jsonrpc; using namespace std; -UnixDomainSocketClient::UnixDomainSocketClient(const std::string& path) - : path(path) + UnixDomainSocketClient::UnixDomainSocketClient(const std::string& path) +: path(path) { } UnixDomainSocketClient::~UnixDomainSocketClient() { - // close(this->socket_fd); } void UnixDomainSocketClient::SendRPCMessage(const std::string& message, std::string& result) throw (JsonRpcException) { - sockaddr_un address; - int nbytes; - char buffer[BUFFER_SIZE]; - socket_fd = socket(AF_UNIX, SOCK_STREAM, 0); + sockaddr_un address; + int socket_fd, nbytes; + char buffer[BUFFER_SIZE]; + socket_fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (socket_fd < 0) + { + throw JsonRpcException(Errors::ERROR_CLIENT_CONNECTOR, "Could not created unix domain socket"); + } - memset(&address, 0, sizeof(sockaddr_un)); + memset(&address, 0, sizeof(sockaddr_un)); address.sun_family = AF_UNIX; snprintf(address.sun_path, PATH_MAX, "%s", this->path.c_str()); - if(connect(socket_fd, (struct sockaddr *) &address, sizeof(sockaddr_un)) != 0) { - throw JsonRpcException(Errors::ERROR_CLIENT_CONNECTOR, "Could not connect to: " + this->path); - } + if(connect(socket_fd, (struct sockaddr *) &address, sizeof(sockaddr_un)) != 0) + { + throw JsonRpcException(Errors::ERROR_CLIENT_CONNECTOR, "Could not connect to: " + this->path); + } - bool fullyWritten = false; - string toSend = message + DELIMITER_CHAR; - do { - ssize_t byteWritten = write(socket_fd, toSend.c_str(), toSend.size()); - if(byteWritten < (ssize_t)toSend.size()) - { - int len = toSend.size() - byteWritten; - toSend = toSend.substr(byteWritten + sizeof(char), len); - } - else - fullyWritten = true; - } while(!fullyWritten); + bool fullyWritten = false; + string toSend = message; + do + { + ssize_t byteWritten = write(socket_fd, toSend.c_str(), toSend.size()); + if(static_cast(byteWritten) < toSend.size()) + { + int len = toSend.size() - byteWritten; + toSend = toSend.substr(byteWritten + sizeof(char), len); + } + else + fullyWritten = true; + } while(!fullyWritten); - do { - nbytes = read(socket_fd, buffer, BUFFER_SIZE); - string tmp; - tmp.append(buffer, nbytes); - result.append(buffer,nbytes); + do + { + nbytes = read(socket_fd, buffer, BUFFER_SIZE); + string tmp; + tmp.append(buffer, nbytes); + result.append(buffer,nbytes); - } while(result.find(DELIMITER_CHAR) == string::npos); + } while(result.find(DELIMITER_CHAR) == string::npos); - result = result.substr(0, result.size()-1); - - close(socket_fd); + close(socket_fd); } diff --git a/src/jsonrpccpp/client/connectors/unixdomainsocketclient.h b/src/jsonrpccpp/client/connectors/unixdomainsocketclient.h index 2a46fde7..78bfbe28 100644 --- a/src/jsonrpccpp/client/connectors/unixdomainsocketclient.h +++ b/src/jsonrpccpp/client/connectors/unixdomainsocketclient.h @@ -15,18 +15,16 @@ namespace jsonrpc { - class UnixDomainSocketClient : public IClientConnector - { - public: - UnixDomainSocketClient(const std::string& path); - virtual ~UnixDomainSocketClient(); - virtual void SendRPCMessage(const std::string& message, std::string& result) throw (JsonRpcException); + class UnixDomainSocketClient : public IClientConnector + { + public: + UnixDomainSocketClient(const std::string& path); + virtual ~UnixDomainSocketClient(); + virtual void SendRPCMessage(const std::string& message, std::string& result) throw (JsonRpcException); - - private: - std::string path; - int socket_fd; - }; + private: + std::string path; + }; } /* namespace jsonrpc */ #endif /* JSONRPC_CPP_HTTPCLIENT_H_ */ diff --git a/src/jsonrpccpp/client/connectors/windowstcpsocketclient.cpp b/src/jsonrpccpp/client/connectors/windowstcpsocketclient.cpp new file mode 100644 index 00000000..4ebdf6f3 --- /dev/null +++ b/src/jsonrpccpp/client/connectors/windowstcpsocketclient.cpp @@ -0,0 +1,290 @@ +/************************************************************************* + * libjson-rpc-cpp + ************************************************************************* + * @file windowstcpsocketclient.cpp + * @date 17.07.2015 + * @author Alexandre Poirot + * @license See attached LICENSE.txt + ************************************************************************/ + +#include "windowstcpsocketclient.h" +#include +#include +#include +#undef _WIN32_WINNT +#define _WIN32_WINNT 0x501 +#include + +#define BUFFER_SIZE 64 +#ifndef DELIMITER_CHAR +#define DELIMITER_CHAR char(0x0A) +#endif //DELIMITER_CHAR + +using namespace jsonrpc; +using namespace std; + +WindowsTcpSocketClient::WindowsTcpSocketClient(const std::string& hostToConnect, const unsigned int &port) : + hostToConnect(hostToConnect), + port(port) +{ +} + +WindowsTcpSocketClient::~WindowsTcpSocketClient() +{ +} + +void WindowsTcpSocketClient::SendRPCMessage(const std::string& message, std::string& result) throw (JsonRpcException) +{ + SOCKET socket_fd = this->Connect(); + char buffer[BUFFER_SIZE]; + bool fullyWritten = false; + string toSend = message; + do + { + int byteWritten = send(socket_fd, toSend.c_str(), toSend.size(), 0); + if(byteWritten == -1) + { + string message = "send() failed"; + int err = WSAGetLastError(); + switch(err) + { + case WSANOTINITIALISED: + case WSAENETDOWN: + case WSAEACCES: + case WSAEINTR: + case WSAEINPROGRESS: + case WSAEFAULT: + case WSAENETRESET: + case WSAENOBUFS: + case WSAENOTCONN: + case WSAENOTSOCK: + case WSAEOPNOTSUPP: + case WSAESHUTDOWN: + case WSAEWOULDBLOCK: + case WSAEMSGSIZE: + case WSAEHOSTUNREACH: + case WSAEINVAL: + case WSAECONNABORTED: + case WSAECONNRESET: + case WSAETIMEDOUT: + message = GetErrorMessage(err); + break; + } + closesocket(socket_fd); + throw JsonRpcException(Errors::ERROR_CLIENT_CONNECTOR, message); + } + else if(static_cast(byteWritten) < toSend.size()) + { + int len = toSend.size() - byteWritten; + toSend = toSend.substr(byteWritten + sizeof(char), len); + } + else + fullyWritten = true; + } while(!fullyWritten); + + do + { + int nbytes = recv(socket_fd, buffer, BUFFER_SIZE, 0); + if(nbytes == -1) + { + string message = "recv() failed"; + int err = WSAGetLastError(); + switch(err) + { + case WSANOTINITIALISED: + case WSAENETDOWN: + case WSAEFAULT: + case WSAENOTCONN: + case WSAEINTR: + case WSAEINPROGRESS: + case WSAENETRESET: + case WSAENOTSOCK: + case WSAEOPNOTSUPP: + case WSAESHUTDOWN: + case WSAEWOULDBLOCK: + case WSAEMSGSIZE: + case WSAEINVAL: + case WSAECONNABORTED: + case WSAETIMEDOUT: + case WSAECONNRESET: + message = GetErrorMessage(err); + break; + } + closesocket(socket_fd); + throw JsonRpcException(Errors::ERROR_CLIENT_CONNECTOR, message); + } + else + { + string tmp; + tmp.append(buffer, nbytes); + result.append(buffer,nbytes); + } + + } while(result.find(DELIMITER_CHAR) == string::npos); + + closesocket(socket_fd); +} + +string WindowsTcpSocketClient::GetErrorMessage(const int& e) +{ + LPVOID lpMsgBuf; + lpMsgBuf = (LPVOID)"Unknown error"; + FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, e, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPTSTR)&lpMsgBuf, 0, NULL); + string message(static_cast(lpMsgBuf)); + LocalFree(lpMsgBuf); + return message; +} + +SOCKET WindowsTcpSocketClient::Connect() throw (JsonRpcException) +{ + if(this->IsIpv4Address(this->hostToConnect)) + { + return this->Connect(this->hostToConnect, this->port); + } + else //We were given a hostname + { + struct addrinfo *result = NULL; + struct addrinfo hints; + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + char port[6]; + port = itoa(this->port, port, 10); + DWORD retval = getaddrinfo(this->hostToConnect.c_str(), port, &hints, &result); + if(retval != 0) + throw JsonRpcException(Errors::ERROR_CLIENT_CONNECTOR, "Could not resolve hostname."); + + bool foundValidIp = false; + SOCKET socket_fd = INVALID_SOCKET; + for(struct addrinfo *temp = result; (temp != NULL) && !foundValidIp; temp = temp->ai_next) + { + if(temp->ai_family == AF_INET) + { + try + { + SOCKADDR_IN* sock = reinterpret_cast(temp->ai_addr); + socket_fd = this->Connect(inet_ntoa(sock->sin_addr), ntohs(sock->sin_port)); + foundValidIp = true; + } + catch(const JsonRpcException& e) + { + foundValidIp = false; + socket_fd = INVALID_SOCKET; + } + catch(void* p) + { + foundValidIp = false; + socket_fd = INVALID_SOCKET; + } + } + } + + if(!foundValidIp) + throw JsonRpcException(Errors::ERROR_CLIENT_CONNECTOR, "Hostname resolved but connection was refused on the given port."); + + return socket_fd; + } +} + +SOCKET WindowsTcpSocketClient::Connect(const string& ip, const int& port) throw (JsonRpcException) +{ + SOCKADDR_IN address; + SOCKET socket_fd = socket(AF_INET, SOCK_STREAM, 0); + if (socket_fd == INVALID_SOCKET) + { + string message = "socket() failed"; + int err = WSAGetLastError(); + switch(err) + { + case WSANOTINITIALISED: + case WSAENETDOWN: + case WSAEAFNOSUPPORT: + case WSAEINPROGRESS: + case WSAEMFILE: + case WSAEINVAL: + case WSAEINVALIDPROVIDER: + case WSAEINVALIDPROCTABLE: + case WSAENOBUFS: + case WSAEPROTONOSUPPORT: + case WSAEPROTOTYPE: + case WSAEPROVIDERFAILEDINIT: + case WSAESOCKTNOSUPPORT: + message = GetErrorMessage(err); + break; + } + throw JsonRpcException(Errors::ERROR_CLIENT_CONNECTOR, message); + } + memset(&address, 0, sizeof(SOCKADDR_IN)); + + address.sin_family = AF_INET; + address.sin_addr.s_addr = inet_addr(ip.c_str()); + address.sin_port = htons(port); + if(connect(socket_fd, reinterpret_cast(&address), sizeof(SOCKADDR_IN)) != 0) + { + string message = "connect() failed"; + int err = WSAGetLastError(); + switch(err) + { + case WSANOTINITIALISED: + case WSAENETDOWN: + case WSAEADDRINUSE: + case WSAEINTR: + case WSAEINPROGRESS: + case WSAEALREADY: + case WSAEADDRNOTAVAIL: + case WSAEAFNOSUPPORT: + case WSAECONNREFUSED: + case WSAEFAULT: + case WSAEINVAL: + case WSAEISCONN: + case WSAENETUNREACH: + case WSAEHOSTUNREACH: + case WSAENOBUFS: + case WSAENOTSOCK: + case WSAETIMEDOUT: + case WSAEWOULDBLOCK: + case WSAEACCES: + message = GetErrorMessage(err); + break; + } + throw JsonRpcException(Errors::ERROR_CLIENT_CONNECTOR, message); + } + return socket_fd; +} + +bool WindowsTcpSocketClient::IsIpv4Address(const std::string& ip) +{ + return (inet_addr(ip.c_str()) != INADDR_NONE); +} + +//This is inspired from SFML to manage Winsock initialization. Thanks to them! ( http://www.sfml-dev.org/ ). +struct ClientSocketInitializer +{ + ClientSocketInitializer() + + { + WSADATA init; + if(WSAStartup(MAKEWORD(2, 2), &init) != 0) + { + throw JsonRpcException(Errors::ERROR_CLIENT_CONNECTOR, "An issue occured while WSAStartup executed."); + } + } + + ~ClientSocketInitializer() + + { + if(WSACleanup() != 0) + { + cerr << "An issue occured while WSAClean executed." << endl; + } + } +}; + +struct ClientSocketInitializer clientGlobalInitializer; diff --git a/src/jsonrpccpp/client/connectors/windowstcpsocketclient.h b/src/jsonrpccpp/client/connectors/windowstcpsocketclient.h new file mode 100644 index 00000000..a1da2e96 --- /dev/null +++ b/src/jsonrpccpp/client/connectors/windowstcpsocketclient.h @@ -0,0 +1,86 @@ +/************************************************************************* + * libjson-rpc-cpp + ************************************************************************* + * @file windowstcpsocketclient.h + * @date 17.07.2015 + * @author Alexandre Poirot + * @license See attached LICENSE.txt + ************************************************************************/ + +#ifndef JSONRPC_CPP_WINDOWSTCPSOCKETCLIENT_H_ +#define JSONRPC_CPP_WINDOWSTCPSOCKETCLIENT_H_ + +#include +#include +#include "tcpsocketclientprivate.h" +#include + +namespace jsonrpc +{ + /** + * This class is the windows implementation of TCPSocketClient. + * It uses the Winsock2 API to performs its job. + */ + class WindowsTcpSocketClient : public TcpSocketClientPrivate + { + public: + /** + * @brief WindowsTcpSocketClient, constructor of the Windows implementation of class TcpSocketClient + * @param hostToConnect The hostname or the ipv4 address on which the client should try to connect + * @param port The port on which the client should try to connect + */ + WindowsTcpSocketClient(const std::string& hostToConnect, const unsigned int &port); + /** + * @brief ~WindowsTcpSocketClient, the destructor of WindowsTcpSocketClient + */ + virtual ~WindowsTcpSocketClient(); + /** + * @brief The real implementation of TcpSocketClient::SendRPCMessage method. + * @param message The message to send + * @param result The result of the call returned by the server + * @throw JsonRpcException Thrown when an issue is encounter with socket manipulation (see message of exception for more information about what happened). + */ + virtual void SendRPCMessage(const std::string& message, std::string& result) throw (JsonRpcException); + + private: + std::string hostToConnect; /*!< The hostname or the ipv4 address on which the client should try to connect*/ + unsigned int port; /*!< The port on which the client should try to connect*/ + /** + * @brief A method to produce human readable messages from Winsock2 error values. + * @param e A Winsock2 error value + * @return The message matching the error value + */ + static std::string GetErrorMessage(const int &e); + /** + * @brief Connects to the host and port provided by constructor parameters. + * + * This method detects if the hostToConnect attribute is either an IPv4 or a hostname. + * On first case it tries to connect to the ip. + * On second case it tries to resolve hostname to an ip and tries to connect to it if resolve was successful. + * + * @returns A file descriptor to the successfully connected socket + * @throw JsonRpcException Thrown when an issue is encountered while trying to connect (see message of exception for more information about what happened). + */ + SOCKET Connect() throw (JsonRpcException); + /** + * @brief Connects to provided ip and port. + * + * This method tries to connect to the provided ip and port. + * + * @param ip The ipv4 address to connect to + * @param port The port to connect to + * @returns A file descriptor to the successfully connected socket + * @throw JsonRpcException Thrown when an issue is encountered while trying to connect (see message of exception for more information about what happened). + */ + SOCKET Connect(const std::string& ip, const int& port) throw (JsonRpcException); + /** + * @brief Check if provided ip is an ipv4 address. + * + * @param ip The ipv4 address to check + * @returns A boolean indicating if the provided ip is or is not an ipv4 address + */ + bool IsIpv4Address(const std::string& ip); + }; + +} /* namespace jsonrpc */ +#endif /* JSONRPC_CPP_WINDOWSTCPSOCKETCLIENT_H_ */ diff --git a/src/jsonrpccpp/client/rpcprotocolclient.cpp b/src/jsonrpccpp/client/rpcprotocolclient.cpp index 2b65f3e7..8f95c67f 100644 --- a/src/jsonrpccpp/client/rpcprotocolclient.cpp +++ b/src/jsonrpccpp/client/rpcprotocolclient.cpp @@ -50,7 +50,7 @@ void RpcProtocolClient::HandleResponse(const std::string &response, Json::Value& } } -int RpcProtocolClient::HandleResponse(const Json::Value &value, Json::Value &result) throw(JsonRpcException) +Json::Value RpcProtocolClient::HandleResponse(const Json::Value &value, Json::Value &result) throw(JsonRpcException) { if(this->ValidateResponse(value)) { @@ -67,7 +67,7 @@ int RpcProtocolClient::HandleResponse(const Json::Value &value, Json::Value &res { throw JsonRpcException(Errors::ERROR_CLIENT_INVALID_RESPONSE, " " + value.toStyledString()); } - return value[KEY_ID].asInt(); + return value[KEY_ID]; } void RpcProtocolClient::BuildRequest(int id, const std::string &method, const Json::Value ¶meter, Json::Value &result, bool isNotification) @@ -91,9 +91,15 @@ void RpcProtocolClient::throwErrorException(const Json::Value &response) { throw JsonRpcException(response[KEY_ERROR][KEY_ERROR_CODE].asInt(), response[KEY_ERROR][KEY_ERROR_MESSAGE].asString(), response[KEY_ERROR][KEY_ERROR_DATA]); } - throw JsonRpcException(response[KEY_ERROR][KEY_ERROR_CODE].asInt(), response[KEY_ERROR][KEY_ERROR_MESSAGE].asString()); + else + { + throw JsonRpcException(response[KEY_ERROR][KEY_ERROR_CODE].asInt(), response[KEY_ERROR][KEY_ERROR_MESSAGE].asString()); + } + } + else + { + throw JsonRpcException(response[KEY_ERROR][KEY_ERROR_CODE].asInt()); } - throw JsonRpcException(response[KEY_ERROR][KEY_ERROR_CODE].asInt()); } bool RpcProtocolClient::ValidateResponse(const Json::Value& response) diff --git a/src/jsonrpccpp/client/rpcprotocolclient.h b/src/jsonrpccpp/client/rpcprotocolclient.h index 268f423c..aa5104be 100644 --- a/src/jsonrpccpp/client/rpcprotocolclient.h +++ b/src/jsonrpccpp/client/rpcprotocolclient.h @@ -58,7 +58,7 @@ namespace jsonrpc { * @param result * @return response id */ - int HandleResponse(const Json::Value &response, Json::Value &result) throw (JsonRpcException); + Json::Value HandleResponse(const Json::Value &response, Json::Value &result) throw (JsonRpcException); static const std::string KEY_PROTOCOL_VERSION; static const std::string KEY_PROCEDURE_NAME; diff --git a/src/jsonrpccpp/server/connectors/linuxtcpsocketserver.cpp b/src/jsonrpccpp/server/connectors/linuxtcpsocketserver.cpp new file mode 100644 index 00000000..d25edc04 --- /dev/null +++ b/src/jsonrpccpp/server/connectors/linuxtcpsocketserver.cpp @@ -0,0 +1,263 @@ +/************************************************************************* + * libjson-rpc-cpp + ************************************************************************* + * @file linuxtcpsocketserver.cpp + * @date 17.07.2015 + * @author Alexandre Poirot + * @license See attached LICENSE.txt + ************************************************************************/ + +#include "linuxtcpsocketserver.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include + +using namespace jsonrpc; +using namespace std; + +#define BUFFER_SIZE 64 +#ifndef DELIMITER_CHAR +#define DELIMITER_CHAR char(0x0A) +#endif //DELIMITER_CHAR + +LinuxTcpSocketServer::LinuxTcpSocketServer(const std::string& ipToBind, const unsigned int &port) : + AbstractServerConnector(), + running(false), + ipToBind(ipToBind), + port(port) +{ +} + +bool LinuxTcpSocketServer::StartListening() +{ + if(!this->running) + { + //Create and bind socket here. + //Then launch the listenning loop. + this->socket_fd = socket(AF_INET, SOCK_STREAM, 0); + if(this->socket_fd < 0) + { + return false; + } + + fcntl(this->socket_fd, F_SETFL, FNDELAY); + int reuseaddr = 1; + setsockopt(this->socket_fd, SOL_SOCKET, SO_REUSEADDR, &reuseaddr, sizeof(reuseaddr)); + + /* start with a clean address structure */ + memset(&(this->address), 0, sizeof(struct sockaddr_in)); + + this->address.sin_family = AF_INET; + inet_aton(this->ipToBind.c_str(), &(this->address.sin_addr)); + this->address.sin_port = htons(this->port); + + if(bind(this->socket_fd, reinterpret_cast(&(this->address)), sizeof(struct sockaddr_in)) != 0) + { + return false; + } + + if(listen(this->socket_fd, 5) != 0) + { + return false; + } + //Launch listening loop there + this->running = true; + int ret = pthread_create(&(this->listenning_thread), NULL, LinuxTcpSocketServer::LaunchLoop, this); + if(ret != 0) + { + pthread_detach(this->listenning_thread); + shutdown(this->socket_fd, 2); + close(this->socket_fd); + } + this->running = static_cast(ret==0); + return this->running; + } + else + { + return false; + } +} + +bool LinuxTcpSocketServer::StopListening() +{ + if(this->running) + { + this->running = false; + pthread_join(this->listenning_thread, NULL); + shutdown(this->socket_fd, 2); + close(this->socket_fd); + return !(this->running); + } + else + { + return false; + } +} + +bool LinuxTcpSocketServer::SendResponse(const string& response, void* addInfo) +{ + bool result = false; + int connection_fd = reinterpret_cast(addInfo); + + string temp = response; + if(temp.find(DELIMITER_CHAR) == string::npos) + { + temp.append(1, DELIMITER_CHAR); + } + if(DELIMITER_CHAR != '\n') + { + char eot = DELIMITER_CHAR; + string toSend = temp.substr(0, toSend.find_last_of('\n')); + toSend += eot; + result = this->WriteToSocket(connection_fd, toSend); + } + else + { + result = this->WriteToSocket(connection_fd, temp); + } + CleanClose(connection_fd); + return result; +} + +void* LinuxTcpSocketServer::LaunchLoop(void *p_data) +{ + pthread_detach(pthread_self()); + LinuxTcpSocketServer *instance = reinterpret_cast(p_data);; + instance->ListenLoop(); + return NULL; +} + +void LinuxTcpSocketServer::ListenLoop() +{ + int connection_fd = 0; + struct sockaddr_in connection_address; + memset(&connection_address, 0, sizeof(struct sockaddr_in)); + socklen_t address_length = sizeof(connection_address); + while(this->running) + { + if((connection_fd = accept(this->socket_fd, reinterpret_cast(&(connection_address)), &address_length)) > 0) + { + pthread_t client_thread; + struct GenerateResponseParameters *params = new struct GenerateResponseParameters(); + params->instance = this; + params->connection_fd = connection_fd; + int ret = pthread_create(&client_thread, NULL, LinuxTcpSocketServer::GenerateResponse, params); + if(ret != 0) + { + pthread_detach(client_thread); + delete params; + params = NULL; + CleanClose(connection_fd); + } + } + else + { + usleep(2500); + } + } +} + +void* LinuxTcpSocketServer::GenerateResponse(void *p_data) +{ + pthread_detach(pthread_self()); + struct GenerateResponseParameters* params = reinterpret_cast(p_data); + LinuxTcpSocketServer *instance = params->instance; + int connection_fd = params->connection_fd; + delete params; + params = NULL; + int nbytes; + char buffer[BUFFER_SIZE]; + string request; + do + { //The client sends its json formatted request and a delimiter request. + nbytes = recv(connection_fd, buffer, BUFFER_SIZE, 0); + if(nbytes == -1) + { + instance->CleanClose(connection_fd); + return NULL; + } + else + { + request.append(buffer,nbytes); + } + } while(request.find(DELIMITER_CHAR) == string::npos); + instance->OnRequest(request, reinterpret_cast(connection_fd)); + return NULL; +} + + +bool LinuxTcpSocketServer::WriteToSocket(const int& fd, const string& toWrite) +{ + bool fullyWritten = false; + bool errorOccured = false; + string toSend = toWrite; + do + { + ssize_t byteWritten = send(fd, toSend.c_str(), toSend.size(), 0); + if(byteWritten < 0) + { + errorOccured = true; + CleanClose(fd); + } + else if(static_cast(byteWritten) < toSend.size()) + { + int len = toSend.size() - byteWritten; + toSend = toSend.substr(byteWritten + sizeof(char), len); + } + else + fullyWritten = true; + } while(!fullyWritten && !errorOccured); + + return fullyWritten && !errorOccured; +} + +bool LinuxTcpSocketServer::WaitClientClose(const int& fd, const int &timeout) +{ + bool ret = false; + int i = 0; + while((recv(fd, NULL, 0, 0) != 0) && i < timeout) + { + usleep(1); + ++i; + ret = true; + } + + return ret; +} + +int LinuxTcpSocketServer::CloseByReset(const int& fd) +{ + struct linger so_linger; + so_linger.l_onoff = 1; + so_linger.l_linger = 0; + + int ret = setsockopt(fd, SOL_SOCKET, SO_LINGER, &so_linger, sizeof(so_linger)); + if(ret != 0) + return ret; + + return close(fd); +} + +int LinuxTcpSocketServer::CleanClose(const int& fd) +{ + if(WaitClientClose(fd)) + { + return close(fd); + } + else + { + return CloseByReset(fd); + } +} diff --git a/src/jsonrpccpp/server/connectors/linuxtcpsocketserver.h b/src/jsonrpccpp/server/connectors/linuxtcpsocketserver.h new file mode 100644 index 00000000..d9461d0b --- /dev/null +++ b/src/jsonrpccpp/server/connectors/linuxtcpsocketserver.h @@ -0,0 +1,144 @@ +/************************************************************************* + * libjson-rpc-cpp + ************************************************************************* + * @file linuxtcpsocketserver.h + * @date 17.07.2015 + * @author Alexandre Poirot + * @license See attached LICENSE.txt + ************************************************************************/ + +#ifndef JSONRPC_CPP_LINUXTCPSOCKETSERVERCONNECTOR_H_ +#define JSONRPC_CPP_LINUXTCPSOCKETSERVERCONNECTOR_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../abstractserverconnector.h" + +namespace jsonrpc +{ + /** + * This class is the Linux/UNIX implementation of TCPSocketServer. + * It uses the POSIX socket API and POSIX thread API to performs its job. + * Each client request is handled in a new thread. + */ + class LinuxTcpSocketServer: public AbstractServerConnector + { + public: + /** + * @brief LinuxTcpSocketServer, constructor of the Linux/UNIX implementation of class TcpSocketServer + * @param ipToBind The ipv4 address on which the server should bind and listen + * @param port The port on which the server should bind and listen + */ + LinuxTcpSocketServer(const std::string& ipToBind, const unsigned int &port); + /** + * @brief The real implementation TcpSocketServer::StartListening method. + * + * This method launches the listening loop that will handle client connections. + * The return value depends on the current listening states : + * - not listening and no error come up while bind and listen returns true + * - not listening but error happen on bind or listen returns false + * - is called while listening returns false + * + * @return A boolean that indicates the success or the failure of the operation. + */ + bool StartListening(); + /** + * @brief The real implementation TcpSocketServer::StopListening method. + * + * This method stops the listening loop that will handle client connections. + * The return value depends on the current listening states : + * - listening and successfuly stops the listen loop returns true + * - is called while not listening returns false + * + * @return A boolean that indicates the success or the failure of the operation. + */ + bool StopListening(); + + /** + * @brief The real implementation TcpSocketServer::SendResponse method. + * + * This method sends the result of the RPC Call over the tcp socket that the client has used to perform its request. + * @param response The response to send to the client + * @param addInfo Additionnal parameters (mainly client socket file descriptor) + * @return A boolean that indicates the success or the failure of the operation. + */ + bool SendResponse(const std::string& response, void* addInfo = NULL); + + private: + bool running; /*!< A boolean that is used to know the listening state*/ + std::string ipToBind; /*!< The ipv4 address on which the server should bind and listen*/ + unsigned int port; /*!< The port on which the server should bind and listen*/ + int socket_fd; /*!< The file descriptior of the listening socket*/ + struct sockaddr_in address; /*!< The listening socket*/ + + pthread_t listenning_thread; /*!< The identifier of the listen loop thread*/ + + /** + * @brief The static method that is used as listening thread entry point + * @param p_data The parameters for the thread entry point method + */ + static void* LaunchLoop(void *p_data); + /** + * @brief The method that launches the listenning loop + */ + void ListenLoop(); + struct GenerateResponseParameters + { + LinuxTcpSocketServer *instance; + int connection_fd; + }; /*!< The structure used to give parameters to the Response generating method*/ + /** + * @brief The static method that is used as client request handling entry point + * @param p_data The parameters for the thread entry point method + */ + static void* GenerateResponse(void *p_data); + /** + * @brief A method that write a message to socket + * + * Tries to send the full message. + * @param fd The file descriptor of the socket message should be sent + * @param toSend The message to send over socket + * @returns A boolean indicating the success or the failure of the operation + */ + bool WriteToSocket(const int& fd, const std::string& toSend); + /** + * @brief A method that wait for the client to close the tcp session + * + * This method wait for the client to close the tcp session in order to avoid the server to enter in TIME_WAIT status. + * Entering in TIME_WAIT status with too many clients may occur in a DOS attack + * since server will not be able to use a new socket when a new client connects. + * @param fd The file descriptor of the socket that should be closed by the client + * @param timeout The maximum time the server will wait for the client to close the tcp session in microseconds. + * @returns A boolean indicating the success or the failure of the operation + */ + bool WaitClientClose(const int& fd, const int &timeout = 100000); + /** + * @brief A method that close a socket by reseting it + * + * This method reset the tcp session in order to avoid enter in TIME_WAIT state. + * @param fd The file descriptor of the socket that should be reset + * @returns The return value of POSIX close() method + */ + int CloseByReset(const int& fd); + /** + * @brief A method that cleanly close a socket by avoid TIME_WAIT state + * + * This method uses WaitClientClose and ClodeByReset to clenly close a tcp session with a client + * (avoiding TIME_WAIT to avoid DOS attacks). + * @param fd The file descriptor of the socket that should be cleanly closed + * @returns The return value of POSIX close() method + */ + int CleanClose(const int& fd); + }; + +} /* namespace jsonrpc */ +#endif /* JSONRPC_CPP_LINUXTCPSOCKETSERVERCONNECTOR_H_ */ + diff --git a/src/jsonrpccpp/server/connectors/tcpsocketserver.cpp b/src/jsonrpccpp/server/connectors/tcpsocketserver.cpp new file mode 100644 index 00000000..e6c23c79 --- /dev/null +++ b/src/jsonrpccpp/server/connectors/tcpsocketserver.cpp @@ -0,0 +1,67 @@ +/************************************************************************* + * libjson-rpc-cpp + ************************************************************************* + * @file tcpsocketserver.cpp + * @date 17.07.2015 + * @author Alexandre Poirot + * @license See attached LICENSE.txt + ************************************************************************/ + +#include "tcpsocketserver.h" +#ifdef __WIN32__ +#include "windowstcpsocketserver.h" +#elif __unix__ +#include "linuxtcpsocketserver.h" +#endif +#include + +using namespace jsonrpc; +using namespace std; + +TcpSocketServer::TcpSocketServer(const std::string& ipToBind, const unsigned int &port) : + AbstractServerConnector() +{ +#ifdef __WIN32__ + this->realSocket = new WindowsTcpSocketServer(ipToBind, port); +#elif __unix__ + this->realSocket = new LinuxTcpSocketServer(ipToBind, port); +#else + this->realSocket = NULL; +#endif +} + +TcpSocketServer::~TcpSocketServer() +{ + if(this->realSocket != NULL) + { + delete this->realSocket; + this->realSocket = NULL; + } +} + +bool TcpSocketServer::StartListening() +{ + if(this->realSocket != NULL) + { + this->realSocket->SetHandler(this->GetHandler()); + return this->realSocket->StartListening(); + } + else + return false; +} + +bool TcpSocketServer::StopListening() +{ + if(this->realSocket != NULL) + return this->realSocket->StopListening(); + else + return false; +} + +bool TcpSocketServer::SendResponse(const string& response, void* addInfo) +{ + if(this->realSocket != NULL) + return this->realSocket->SendResponse(response, addInfo); + else + return false; +} diff --git a/src/jsonrpccpp/server/connectors/tcpsocketserver.h b/src/jsonrpccpp/server/connectors/tcpsocketserver.h new file mode 100644 index 00000000..2c9c69f7 --- /dev/null +++ b/src/jsonrpccpp/server/connectors/tcpsocketserver.h @@ -0,0 +1,80 @@ +/************************************************************************* + * libjson-rpc-cpp + ************************************************************************* + * @file tcpsocketserver.h + * @date 17.07.2015 + * @author Alexandre Poirot + * @license See attached LICENSE.txt + ************************************************************************/ + +#ifndef JSONRPC_CPP_TCPSOCKETSERVERCONNECTOR_H_ +#define JSONRPC_CPP_TCPSOCKETSERVERCONNECTOR_H_ + +#include "../abstractserverconnector.h" + +namespace jsonrpc +{ + /** + * This class provides an embedded TCP Socket Server that handle incoming Requests and send result over same socket. + * It uses the delimiter character to distinct a full RPC Message over the tcp flow. This character is parametered on + * compilation time in implementation files. The default value for this delimiter is 0x0A a.k.a. "new line". + * This class hides OS specific features in real implementation of this server. Currently it has implementation for + * both Linux and Windows. + */ + class TcpSocketServer: public AbstractServerConnector + { + public: + /** + * @brief TcpSocketServer, constructor for the included TcpSocketServer + * + * Instanciates the real implementation of TcpSocketServerPrivate depending on running OS. + * + * @param ipToBind The ipv4 address on which the server should bind and listen + * @param port The port on which the server should bind and listen + */ + TcpSocketServer(const std::string& ipToBind, const unsigned int &port); + /** + * @brief ~TcpSocketServer, the destructor of TcpSocketServer + */ + ~TcpSocketServer(); + /** + * @brief The AbstractServerConnector::StartListening method overload. + * + * This method launches the listening loop that will handle client connections. + * The return value depends on the current listening states : + * - not listening and no error come up while bind and listen returns true + * - not listening but error happen on bind or listen returns false + * - is called while listening returns false + * + * @return A boolean that indicates the success or the failure of the operation. + */ + bool StartListening(); + /** + * @brief The AbstractServerConnector::StopListening method overload. + * + * This method stops the listening loop that will handle client connections. + * The return value depends on the current listening states : + * - listening and successfuly stops the listen loop returns true + * - is called while not listening returns false + * + * @return A boolean that indicates the success or the failure of the operation. + */ + bool StopListening(); + + /** + * @brief The AbstractServerConnector::SendResponse method overload. + * + * This method sends the result of the RPC Call over the tcp socket that the client has used to perform its request. + * @param response The response to send to the client + * @param addInfo Additionnal parameters (mainly client socket file descriptor) + * @return A boolean that indicates the success or the failure of the operation. + */ + bool SendResponse(const std::string& response, void* addInfo = NULL); + + private: + AbstractServerConnector *realSocket; /*!< A pointer to the real implementation of this class depending of running OS*/ + }; + +} /* namespace jsonrpc */ +#endif /* JSONRPC_CPP_TCPSOCKETSERVERCONNECTOR_H_ */ + diff --git a/src/jsonrpccpp/server/connectors/unixdomainsocketserver.cpp b/src/jsonrpccpp/server/connectors/unixdomainsocketserver.cpp index cda80636..9fca57b6 100644 --- a/src/jsonrpccpp/server/connectors/unixdomainsocketserver.cpp +++ b/src/jsonrpccpp/server/connectors/unixdomainsocketserver.cpp @@ -10,6 +10,7 @@ #include "unixdomainsocketserver.h" #include #include +#include #include #include #include @@ -23,12 +24,12 @@ using namespace std; #define BUFFER_SIZE 1024 #define PATH_MAX 108 #ifndef DELIMITER_CHAR - #define DELIMITER_CHAR char(0x0A) +#define DELIMITER_CHAR char(0x0A) #endif UnixDomainSocketServer::UnixDomainSocketServer(const string &socket_path) : - running(false), - socket_path(socket_path.substr(0, PATH_MAX)) + running(false), + socket_path(socket_path.substr(0, PATH_MAX)) { } @@ -36,40 +37,69 @@ bool UnixDomainSocketServer::StartListening() { if(!this->running) { - if (access(this->socket_path.c_str(), F_OK) != -1) - return false; + //Create and bind socket here. + //Then launch the listenning loop. + if (access(this->socket_path.c_str(), F_OK) != -1) + return false; + + this->socket_fd = socket(AF_UNIX, SOCK_STREAM, 0); + + if(this->socket_fd < 0) + { + return false; + } - this->socket_fd = socket(AF_UNIX, SOCK_STREAM, 0); + unlink(this->socket_path.c_str()); - fcntl(this->socket_fd, F_SETFL, FNDELAY); + fcntl(this->socket_fd, F_SETFL, FNDELAY); + /* start with a clean address structure */ memset(&(this->address), 0, sizeof(struct sockaddr_un)); this->address.sun_family = AF_UNIX; snprintf(this->address.sun_path, PATH_MAX, "%s", this->socket_path.c_str()); - bind(this->socket_fd, reinterpret_cast(&(this->address)), sizeof(struct sockaddr_un)); + if(bind(this->socket_fd, reinterpret_cast(&(this->address)), sizeof(struct sockaddr_un)) != 0) + { + return false; + } - listen(this->socket_fd, 5); + if(listen(this->socket_fd, 5) != 0) + { + return false; + } //Launch listening loop there - this->running = true; - return (pthread_create(&(this->listenning_thread), NULL, UnixDomainSocketServer::LaunchLoop, this) == 0); + this->running = true; + int ret = pthread_create(&(this->listenning_thread), NULL, UnixDomainSocketServer::LaunchLoop, this); + if(ret != 0) + { + pthread_detach(this->listenning_thread); + } + this->running = static_cast(ret==0); + + return this->running; + } + else + { + return false; } - return false; } bool UnixDomainSocketServer::StopListening() { if(this->running) { - this->running = false; - pthread_join(this->listenning_thread, NULL); + this->running = false; + pthread_join(this->listenning_thread, NULL); close(this->socket_fd); unlink(this->socket_path.c_str()); - return true; + return !(this->running); + } + else + { + return false; } - return false; } bool UnixDomainSocketServer::SendResponse(const string& response, void* addInfo) @@ -78,53 +108,64 @@ bool UnixDomainSocketServer::SendResponse(const string& response, void* addInfo) int connection_fd = reinterpret_cast(addInfo); string temp = response; - if(temp.find(DELIMITER_CHAR) == string::npos) { + if(temp.find(DELIMITER_CHAR) == string::npos) + { temp.append(1, DELIMITER_CHAR); } - if(DELIMITER_CHAR != '\n') { + if(DELIMITER_CHAR != '\n') + { char eot = DELIMITER_CHAR; string toSend = temp.substr(0, toSend.find_last_of('\n')); toSend += eot; result = this->WriteToSocket(connection_fd, toSend); } - else { + else + { result = this->WriteToSocket(connection_fd, temp); } close(connection_fd); return result; } -void* UnixDomainSocketServer::LaunchLoop(void *p_data) { +void* UnixDomainSocketServer::LaunchLoop(void *p_data) +{ + pthread_detach(pthread_self()); UnixDomainSocketServer *instance = reinterpret_cast(p_data);; instance->ListenLoop(); - return NULL; + return NULL; } -void UnixDomainSocketServer::ListenLoop() { +void UnixDomainSocketServer::ListenLoop() +{ int connection_fd; socklen_t address_length = sizeof(this->address); - while(this->running) + while(this->running) { - connection_fd = accept(this->socket_fd, (struct sockaddr *) &(this->address), &address_length); - if (connection_fd > 0) - { - pthread_t client_thread; - struct ClientConnection *params = new struct ClientConnection(); - params->instance = this; - params->connection_fd = connection_fd; - pthread_create(&client_thread, NULL, UnixDomainSocketServer::GenerateResponse, params); - } - else - { - usleep(25000); - } - + if((connection_fd = accept(this->socket_fd, reinterpret_cast(&(this->address)), &address_length)) > 0) + { + pthread_t client_thread; + struct ClientConnection *params = new struct ClientConnection(); + params->instance = this; + params->connection_fd = connection_fd; + int ret = pthread_create(&client_thread, NULL, UnixDomainSocketServer::GenerateResponse, params); + if(ret != 0) + { + pthread_detach(client_thread); + delete params; + params = NULL; + } + } + else + { + usleep(25000); + } } } -void* UnixDomainSocketServer::GenerateResponse(void *p_data) { - pthread_detach(pthread_self()); - struct ClientConnection* params = reinterpret_cast(p_data); +void* UnixDomainSocketServer::GenerateResponse(void *p_data) +{ + pthread_detach(pthread_self()); + struct ClientConnection* params = reinterpret_cast(p_data); UnixDomainSocketServer *instance = params->instance; int connection_fd = params->connection_fd; delete params; @@ -132,26 +173,29 @@ void* UnixDomainSocketServer::GenerateResponse(void *p_data) { int nbytes; char buffer[BUFFER_SIZE]; string request; - do { //The client sends its json formatted request and a delimiter request. + do + { //The client sends its json formatted request and a delimiter request. nbytes = read(connection_fd, buffer, BUFFER_SIZE); request.append(buffer,nbytes); } while(request.find(DELIMITER_CHAR) == string::npos); - instance->OnRequest(request.substr(0, request.size()-1), reinterpret_cast(connection_fd)); - pthread_exit(NULL); - return NULL; + instance->OnRequest(request, reinterpret_cast(connection_fd)); + return NULL; } -bool UnixDomainSocketServer::WriteToSocket(int fd, const string& toWrite) { +bool UnixDomainSocketServer::WriteToSocket(int fd, const string& toWrite) +{ bool fullyWritten = false; bool errorOccured = false; string toSend = toWrite; - do { - ssize_t byteWritten = write(fd, toSend.c_str(), toSend.size()); + do + { + ssize_t byteWritten = write(fd, toSend.c_str(), toSend.size()); if(byteWritten < 0) errorOccured = true; - else if(byteWritten < (ssize_t) toSend.size()) { + else if(byteWritten < static_cast(toSend.size())) + { int len = toSend.size() - byteWritten; toSend = toSend.substr(byteWritten + sizeof(char), len); } diff --git a/src/jsonrpccpp/server/connectors/unixdomainsocketserver.h b/src/jsonrpccpp/server/connectors/unixdomainsocketserver.h index e92f02a6..2d2072b0 100644 --- a/src/jsonrpccpp/server/connectors/unixdomainsocketserver.h +++ b/src/jsonrpccpp/server/connectors/unixdomainsocketserver.h @@ -18,7 +18,6 @@ #include #include #include -#include #include "../abstractserverconnector.h" @@ -30,7 +29,6 @@ namespace jsonrpc class UnixDomainSocketServer: public AbstractServerConnector { public: - /** * @brief UnixDomainSocketServer, constructor for the included UnixDomainSocketServer * @param socket_path, a string containing the path to the unix socket @@ -42,7 +40,6 @@ namespace jsonrpc bool virtual SendResponse(const std::string& response, void* addInfo = NULL); - private: bool running; std::string socket_path; @@ -50,11 +47,11 @@ namespace jsonrpc struct sockaddr_un address; pthread_t listenning_thread; - std::set client_thread_pool; static void* LaunchLoop(void *p_data); void ListenLoop(); - struct ClientConnection { + struct ClientConnection + { UnixDomainSocketServer *instance; int connection_fd; }; diff --git a/src/jsonrpccpp/server/connectors/windowstcpsocketserver.cpp b/src/jsonrpccpp/server/connectors/windowstcpsocketserver.cpp new file mode 100644 index 00000000..95dcbc2d --- /dev/null +++ b/src/jsonrpccpp/server/connectors/windowstcpsocketserver.cpp @@ -0,0 +1,292 @@ +/************************************************************************* + * libjson-rpc-cpp + ************************************************************************* + * @file windowstcpsocketserver.cpp + * @date 17.07.2015 + * @author Alexandre Poirot + * @license See attached LICENSE.txt + ************************************************************************/ + +#include "windowstcpsocketserver.h" + +#include +#include +#include +#include + +#include +#include +#include + +#include + +using namespace jsonrpc; +using namespace std; + +#define BUFFER_SIZE 64 +#ifndef DELIMITER_CHAR +#define DELIMITER_CHAR char(0x0A) +#endif //DELIMITER_CHAR + +WindowsTcpSocketServer::WindowsTcpSocketServer(const std::string& ipToBind, const unsigned int &port) : + AbstractServerConnector(), + ipToBind(ipToBind), + port(port), + running(false) +{ +} + +WindowsTcpSocketServer::~WindowsTcpSocketServer() +{ +} + +bool WindowsTcpSocketServer::StartListening() +{ + if(!this->running) + { + //Create and bind socket here. + //Then launch the listenning loop. + this->socket_fd = socket(AF_INET, SOCK_STREAM, 0); + if(this->socket_fd < 0) + { + return false; + } + unsigned long nonBlocking = 1; + ioctlsocket(this->socket_fd, FIONBIO, &nonBlocking); //Set non blocking + int reuseaddr = 1; + setsockopt(this->socket_fd, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast(&reuseaddr), sizeof(reuseaddr)); + + /* start with a clean address structure */ + memset(&(this->address), 0, sizeof(SOCKADDR_IN)); + + this->address.sin_family = AF_INET; + this->address.sin_addr.s_addr = inet_addr(this->ipToBind.c_str()); + this->address.sin_port = htons(this->port); + + if(bind(this->socket_fd, reinterpret_cast(&(this->address)), sizeof(SOCKADDR_IN)) != 0) + { + return false; + } + + if(listen(this->socket_fd, 5) != 0) + { + return false; + } + //Launch listening loop there + this->running = true; + HANDLE ret = CreateThread(NULL, 0, reinterpret_cast(&(WindowsTcpSocketServer::LaunchLoop)), reinterpret_cast(this), 0, &(this->listenning_thread)); + if(ret == NULL) + { + ExitProcess(3); + } + else + { + CloseHandle(ret); + } + this->running = static_cast(ret!=NULL); + return this->running; + } + else + { + return false; + } + +} + +bool WindowsTcpSocketServer::StopListening() +{ + if(this->running) + { + this->running = false; + WaitForSingleObject(OpenThread(THREAD_ALL_ACCESS, FALSE,this->listenning_thread), INFINITE); + closesocket(this->socket_fd); + return !(this->running); + } + else + { + return false; + } +} + +bool WindowsTcpSocketServer::SendResponse(const string& response, void* addInfo) +{ + bool result = false; + int connection_fd = reinterpret_cast(addInfo); + + string temp = response; + if(temp.find(DELIMITER_CHAR) == string::npos) + { + temp.append(1, DELIMITER_CHAR); + } + if(DELIMITER_CHAR != '\n') + { + char eot = DELIMITER_CHAR; + string toSend = temp.substr(0, toSend.find_last_of('\n')); + toSend += eot; + result = this->WriteToSocket(connection_fd, toSend); + } + else + { + result = this->WriteToSocket(connection_fd, temp); + } + CleanClose(connection_fd); + return result; +} + +DWORD WINAPI WindowsTcpSocketServer::LaunchLoop(LPVOID lp_data) +{ + WindowsTcpSocketServer *instance = reinterpret_cast(lp_data);; + instance->ListenLoop(); + CloseHandle(GetCurrentThread()); + return 0; //DO NOT USE ExitThread function here! ExitThread does not call destructors for allocated objects and therefore it would lead to a memory leak. +} + +void WindowsTcpSocketServer::ListenLoop() +{ + while(this->running) + { + SOCKET connection_fd = INVALID_SOCKET; + SOCKADDR_IN connection_address; + memset(&connection_address, 0, sizeof(SOCKADDR_IN)); + int address_length = sizeof(connection_address); + if((connection_fd = accept(this->socket_fd, reinterpret_cast(&connection_address), &address_length)) != INVALID_SOCKET) + { + unsigned long nonBlocking = 0; + ioctlsocket(connection_fd, FIONBIO, &nonBlocking); //Set blocking + DWORD client_thread; + struct GenerateResponseParameters *params = new struct GenerateResponseParameters(); + params->instance = this; + params->connection_fd = connection_fd; + HANDLE ret = CreateThread(NULL, 0, reinterpret_cast(&(WindowsTcpSocketServer::GenerateResponse)), reinterpret_cast(params), 0, &client_thread); + if(ret == NULL) + { + delete params; + params = NULL; + CleanClose(connection_fd); + } + else + { + CloseHandle(ret); + } + } + else + { + Sleep(2.5); + } + } +} + +DWORD WINAPI WindowsTcpSocketServer::GenerateResponse(LPVOID lp_data) +{ + struct GenerateResponseParameters* params = reinterpret_cast(lp_data); + WindowsTcpSocketServer *instance = params->instance; + int connection_fd = params->connection_fd; + delete params; + params = NULL; + int nbytes = 0; + char buffer[BUFFER_SIZE]; + memset(&buffer, 0, BUFFER_SIZE); + string request = ""; + do + { //The client sends its json formatted request and a delimiter request. + nbytes = recv(connection_fd, buffer, BUFFER_SIZE, 0); + if(nbytes == -1) + { + instance->CleanClose(connection_fd); + } + else + { + request.append(buffer,nbytes); + } + } while(request.find(DELIMITER_CHAR) == string::npos); + instance->OnRequest(request, reinterpret_cast(connection_fd)); + CloseHandle(GetCurrentThread()); + return 0; //DO NOT USE ExitThread function here! ExitThread does not call destructors for allocated objects and therefore it would lead to a memory leak. +} + +bool WindowsTcpSocketServer::WriteToSocket(const SOCKET& fd, const string& toWrite) +{ + bool fullyWritten = false; + bool errorOccured = false; + string toSend = toWrite; + do + { + ssize_t byteWritten = send(fd, toSend.c_str(), toSend.size(), 0); + if(byteWritten < 0) + { + errorOccured = true; + CleanClose(fd); + } + else if(byteWritten < toSend.size()) + { + int len = toSend.size() - byteWritten; + toSend = toSend.substr(byteWritten + sizeof(char), len); + } + else + fullyWritten = true; + } while(!fullyWritten && !errorOccured); + + return fullyWritten && !errorOccured; +} + +bool WindowsTcpSocketServer::WaitClientClose(const SOCKET& fd, const int &timeout) +{ + bool ret = false; + int i = 0; + while((recv(fd, NULL, 0, 0) != 0) && i < timeout) + { + Sleep(1); + ++i; + ret = true; + } + + return ret; +} + +int WindowsTcpSocketServer::CloseByReset(const SOCKET& fd) +{ + struct linger so_linger; + so_linger.l_onoff = 1; + so_linger.l_linger = 0; + + int ret = setsockopt(fd, SOL_SOCKET, SO_LINGER, reinterpret_cast(&so_linger), sizeof(so_linger)); + if(ret != 0) + return ret; + + return closesocket(fd); +} + +int WindowsTcpSocketServer::CleanClose(const SOCKET& fd) +{ + if(WaitClientClose(fd)) + { + return closesocket(fd); + } + else + { + return CloseByReset(fd); + } +} + +//This is inspired from SFML to manage Winsock initialization. Thanks to them! ( http://www.sfml-dev.org/ ). +struct ServerSocketInitializer +{ + ServerSocketInitializer() + { + WSADATA init; + if(WSAStartup(MAKEWORD(2, 2), &init) != 0) + { + JsonRpcException(Errors::ERROR_CLIENT_CONNECTOR, "An issue occured while WSAStartup executed."); + } + } + + ~ServerSocketInitializer() + { + if(WSACleanup() != 0) + { + cerr << "An issue occured while WSAClean executed." << endl; + } + } +}; + +struct ServerSocketInitializer serverGlobalInitializer; diff --git a/src/jsonrpccpp/server/connectors/windowstcpsocketserver.h b/src/jsonrpccpp/server/connectors/windowstcpsocketserver.h new file mode 100644 index 00000000..88f0e80e --- /dev/null +++ b/src/jsonrpccpp/server/connectors/windowstcpsocketserver.h @@ -0,0 +1,142 @@ +/************************************************************************* + * libjson-rpc-cpp + ************************************************************************* + * @file windowstcpsocketserver.h + * @date 17.07.2015 + * @author Alexandre Poirot + * @license See attached LICENSE.txt + ************************************************************************/ + +#ifndef JSONRPC_CPP_WINDOWSTCPSOCKETSERVERCONNECTOR_H_ +#define JSONRPC_CPP_WINDOWSTCPSOCKETSERVERCONNECTOR_H_ + +#include +#include +#include + +#include "../abstractserverconnector.h" + +namespace jsonrpc +{ + /** + * This class is the Windows implementation of TCPSocketServer. + * It uses the Winsock2 socket API and Windows thread API to performs its job. + * Each client request is handled in a new thread. + */ + class WindowsTcpSocketServer: public AbstractServerConnector + { + public: + /** + * @brief WindowsTcpSocketServer, constructor of the Windows implementation of class TcpSocketServer + * @param ipToBind The ipv4 address on which the server should bind and listen + * @param port The port on which the server should bind and listen + */ + WindowsTcpSocketServer(const std::string& ipToBind, const unsigned int &port); + /** + * @brief ~WindowsTcpSocketServer, the destructor of WindowsTcpSocketServer + */ + ~WindowsTcpSocketServer(); + /** + * @brief The real implementation TcpSocketServer::StartListening method. + * + * This method launches the listening loop that will handle client connections. + * The return value depends on the current listening states : + * - not listening and no error come up while bind and listen returns true + * - not listening but error happen on bind or listen returns false + * - is called while listening returns false + * + * @return A boolean that indicates the success or the failure of the operation. + */ + bool StartListening(); + /** + * @brief The real implementation TcpSocketServer::StopListening method. + * + * This method stops the listening loop that will handle client connections. + * The return value depends on the current listening states : + * - listening and successfuly stops the listen loop returns true + * - is called while not listening returns false + * + * @return A boolean that indicates the success or the failure of the operation. + */ + bool StopListening(); + + /** + * @brief The real implementation TcpSocketServer::SendResponse method. + * + * This method sends the result of the RPC Call over the tcp socket that the client has used to perform its request. + * @param response The response to send to the client + * @param addInfo Additionnal parameters (mainly client socket file descriptor) + * @return A boolean that indicates the success or the failure of the operation. + */ + bool SendResponse(const std::string& response, void* addInfo = NULL); + + private: + bool running; /*!< A boolean that is used to know the listening state*/ + std::string ipToBind; /*!< The ipv4 address on which the server should bind and listen*/ + unsigned int port; /*!< The port on which the server should bind and listen*/ + SOCKET socket_fd; /*!< The file descriptior of the listening socket*/ + SOCKADDR_IN address; /*!< The listening socket*/ + + DWORD listenning_thread; /*!< The identifier of the listen loop thread*/ + + /** + * @brief The static method that is used as listening thread entry point + * @param lp_data The parameters for the thread entry point method + */ + static DWORD WINAPI LaunchLoop(LPVOID lp_data); + /** + * @brief The method that launches the listenning loop + */ + void ListenLoop(); + struct GenerateResponseParameters + { + WindowsTcpSocketServer *instance; + SOCKET connection_fd; + }; /*!< The structure used to give parameters to the Response generating method*/ + /** + * @brief The static method that is used as client request handling entry point + * @param lp_data The parameters for the thread entry point method + */ + static DWORD WINAPI GenerateResponse(LPVOID lp_data); + /** + * @brief A method that write a message to socket + * + * Tries to send the full message. + * @param fd The file descriptor of the socket message should be sent + * @param toSend The message to send over socket + * @returns A boolean indicating the success or the failure of the operation + */ + bool WriteToSocket(const SOCKET& fd, const std::string& toSend); + /** + * @brief A method that wait for the client to close the tcp session + * + * This method wait for the client to close the tcp session in order to avoid the server to enter in TIME_WAIT status. + * Entering in TIME_WAIT status with too many clients may occur in a DOS attack + * since server will not be able to use a new socket when a new client connects. + * @param fd The file descriptor of the socket that should be closed by the client + * @param timeout The maximum time the server will wait for the client to close the tcp session in milliseconds. + * @returns A boolean indicating the success or the failure of the operation + */ + bool WaitClientClose(const SOCKET& fd, const int &timeout = 100); + /** + * @brief A method that close a socket by reseting it + * + * This method reset the tcp session in order to avoid enter in TIME_WAIT state. + * @param fd The file descriptor of the socket that should be reset + * @returns The return value of POSIX close() method + */ + int CloseByReset(const SOCKET& fd); + /** + * @brief A method that cleanly close a socket by avoid TIME_WAIT state + * + * This method uses WaitClientClose and ClodeByReset to clenly close a tcp session with a client + * (avoiding TIME_WAIT to avoid DOS attacks). + * @param fd The file descriptor of the socket that should be cleanly closed + * @returns The return value of POSIX close() method + */ + int CleanClose(const SOCKET& fd); + }; + +} /* namespace jsonrpc */ +#endif /* JSONRPC_CPP_WINDOWSTCPSOCKETSERVERCONNECTOR_H_ */ + diff --git a/src/stubgenerator/CMakeLists.txt b/src/stubgenerator/CMakeLists.txt index a5e55647..f9dbe4c4 100644 --- a/src/stubgenerator/CMakeLists.txt +++ b/src/stubgenerator/CMakeLists.txt @@ -40,7 +40,7 @@ add_executable(jsonrpcstub main.cpp) target_link_libraries(jsonrpcstub jsonrpccommon libjsonrpcstub ) #Generate manpage -if (${CMAKE_SYSTEM_NAME} MATCHES "Linux") +if (NOT ${CMAKE_SYSTEM_NAME} STREQUAL "Windows") configure_file("${CMAKE_SOURCE_DIR}/doc/manpage.in" "${CMAKE_BINARY_DIR}/manpage" @ONLY) add_custom_command(OUTPUT jsonrpcstub.1.gz COMMAND gzip -c "${CMAKE_BINARY_DIR}/manpage" > ${CMAKE_BINARY_DIR}/jsonrpcstub.1.gz @@ -52,7 +52,7 @@ if (${CMAKE_SYSTEM_NAME} MATCHES "Linux") install(FILES ${CMAKE_BINARY_DIR}/jsonrpcstub.1.gz DESTINATION share/man/man1/) add_dependencies(jsonrpcstub manpage) -endif(${CMAKE_SYSTEM_NAME} MATCHES "Linux") +endif() CONFIGURE_FILE(${CMAKE_SOURCE_DIR}/cmake/libjsonrpccpp-stub.pc.cmake ${CMAKE_BINARY_DIR}/libjsonrpccpp-stub.pc) @@ -64,8 +64,9 @@ install(DIRECTORY ${CMAKE_SOURCE_DIR}/src/stubgenerator/ DESTINATION include/jsonrpccpp/stubgen FILES_MATCHING PATTERN "*.h") -install(TARGETS jsonrpcstub ${ALL_LIBS} LIBRARY DESTINATION lib${LIB_SUFFIX} - ARCHIVE DESTINATION lib${LIB_SUFFIX} - RUNTIME DESTINATION bin +install(TARGETS ${ALL_LIBS} jsonrpcstub + LIBRARY DESTINATION lib${LIB_SUFFIX}/${CMAKE_LIBRARY_PATH} + ARCHIVE DESTINATION lib${LIB_SUFFIX}/${CMAKE_LIBRARY_PATH} + RUNTIME DESTINATION bin ) diff --git a/src/stubgenerator/client/cppclientstubgenerator.h b/src/stubgenerator/client/cppclientstubgenerator.h index 2dc60353..7900eb0f 100755 --- a/src/stubgenerator/client/cppclientstubgenerator.h +++ b/src/stubgenerator/client/cppclientstubgenerator.h @@ -25,7 +25,6 @@ namespace jsonrpc virtual void generateStub(); - private: void generateMethod(Procedure& proc); void generateAssignments(Procedure& proc); void generateProcCall(Procedure &proc); diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt index dfd66fda..9438134f 100644 --- a/src/test/CMakeLists.txt +++ b/src/test/CMakeLists.txt @@ -37,6 +37,10 @@ if(UNIX_DOMAIN_SOCKET_SERVER AND UNIX_DOMAIN_SOCKET_CLIENT) add_definitions(-DUNIXDOMAINSOCKET_TESTING) endif() +if(TCP_SOCKET_SERVER AND TCP_SOCKET_CLIENT) + add_definitions(-DTCPSOCKET_TESTING) +endif() + if(COMPILE_STUBGEN) add_definitions(-DSTUBGEN_TESTING) file(GLOB test_specs *.json) @@ -73,6 +77,10 @@ if (UNIX_DOMAIN_SOCKET_CLIENT AND UNIX_DOMAIN_SOCKET_SERVER) add_test(NAME connector_unixdomainsocket WORKING_DIRECTORY ${CMAKE_BINARY_DIR} COMMAND ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/unit_testsuite "[connector_unixdomainsocket]") endif() +if (TCP_SOCKET_CLIENT AND TCP_SOCKET_SERVER) + add_test(NAME connector_tcpsocket WORKING_DIRECTORY ${CMAKE_BINARY_DIR} COMMAND ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/unit_testsuite "[connector_tcpsocket]") +endif() + if(COMPILE_STUBGEN) add_test(NAME stubgen WORKING_DIRECTORY ${CMAKE_BINARY_DIR} COMMAND ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/unit_testsuite "[stubgenerator]") endif() diff --git a/src/test/test_client.cpp b/src/test/test_client.cpp index 46eb60f0..0676825a 100644 --- a/src/test/test_client.cpp +++ b/src/test/test_client.cpp @@ -55,7 +55,17 @@ namespace testclient { } using namespace testclient; +TEST_CASE_METHOD(F, "test_client_id", TEST_MODULE) +{ + params.append(33); + c.SetResponse("{\"jsonrpc\":\"2.0\", \"id\": \"1\", \"result\": 23}"); + Json::Value response = client.CallMethod("abcd", params); + CHECK(response.asInt() == 23); + c.SetResponse("{\"jsonrpc\":\"2.0\", \"id\": 1, \"result\": 24}"); + response = client.CallMethod("abcd", params); + CHECK(response.asInt() == 24); +} TEST_CASE_METHOD(F, "test_client_v2_method_success", TEST_MODULE) { diff --git a/src/test/test_connector_tcpsocket.cpp b/src/test/test_connector_tcpsocket.cpp new file mode 100644 index 00000000..920077bf --- /dev/null +++ b/src/test/test_connector_tcpsocket.cpp @@ -0,0 +1,94 @@ +/************************************************************************* + * libjson-rpc-cpp + ************************************************************************* + * @file test_connector_tcpsocket.cpp + * @date 27/07/2015 + * @author Alexandre Poirot + * @license See attached LICENSE.txt + ************************************************************************/ + +#ifdef TCPSOCKET_TESTING +#include +#include +#include +#include "mockclientconnectionhandler.h" + +#include "checkexception.h" + +using namespace jsonrpc; +using namespace std; + +#ifndef DELIMITER_CHAR + #define DELIMITER_CHAR char(0x0A) +#endif + +#define TEST_MODULE "[connector_tcpsocket]" + +#define IP "127.0.0.1" +#define PORT 50000 + +namespace testtcpsocketserver +{ + struct F { + TcpSocketServer server; + TcpSocketClient client; + MockClientConnectionHandler handler; + + F() : + server(IP, PORT), + client(IP, PORT) + { + server.SetHandler(&handler); + REQUIRE(server.StartListening()); + } + ~F() + { + server.StopListening(); + } + }; + + bool check_exception1(JsonRpcException const&ex) + { + return ex.GetCode() == Errors::ERROR_CLIENT_CONNECTOR; + } +} +using namespace testtcpsocketserver; + +TEST_CASE_METHOD(F, "test_tcpsocket_success", TEST_MODULE) +{ + handler.response = "exampleresponse"; + handler.timeout = 100; + string result; + string request = "examplerequest"; + request.push_back(DELIMITER_CHAR); + string expectedResult = "exampleresponse"; + expectedResult.push_back(DELIMITER_CHAR); + + client.SendRPCMessage(request, result); + + CHECK(handler.request == request); + CHECK(result == expectedResult); +} + +TEST_CASE("test_tcpsocket_server_multiplestart", TEST_MODULE) +{ + TcpSocketServer server(IP, PORT); + CHECK(server.StartListening() == true); + CHECK(server.StartListening() == false); + + TcpSocketServer server2(IP, PORT); + CHECK(server2.StartListening() == false); + CHECK(server2.StopListening() == false); + + CHECK(server.StopListening() == true); +} + + +TEST_CASE("test_tcpsocket_client_invalid", TEST_MODULE) +{ + TcpSocketClient client("127.0.0.1", 40000); //If this test fails, check that port 40000 is really unused. If it is used, change this port value to an unused port, recompile tests and run tests again. + string result; + CHECK_EXCEPTION_TYPE(client.SendRPCMessage("foobar", result), JsonRpcException, check_exception1); +} + +#endif diff --git a/src/test/test_connector_unixdomainsocket.cpp b/src/test/test_connector_unixdomainsocket.cpp index 561fec39..bcd27108 100644 --- a/src/test/test_connector_unixdomainsocket.cpp +++ b/src/test/test_connector_unixdomainsocket.cpp @@ -18,6 +18,10 @@ using namespace jsonrpc; using namespace std; +#ifndef DELIMITER_CHAR + #define DELIMITER_CHAR char(0x0A) +#endif + #define TEST_MODULE "[connector_unixdomainsocket]" #define SOCKET_PATH "/tmp/jsonrpccpp-socket" @@ -50,15 +54,20 @@ namespace testunixdomainsocketserver } using namespace testunixdomainsocketserver; - TEST_CASE_METHOD(F, "test_unixdomainsocket_success", TEST_MODULE) { handler.response = "exampleresponse"; + handler.timeout = 100; string result; - client.SendRPCMessage("examplerequest", result); + string request = "examplerequest"; + request.push_back(DELIMITER_CHAR); + string expectedResult = "exampleresponse"; + expectedResult.push_back(DELIMITER_CHAR); + + client.SendRPCMessage(request, result); - CHECK(handler.request == "examplerequest"); - CHECK(result == "exampleresponse"); + CHECK(handler.request == request); + CHECK(result == expectedResult); }