diff --git a/.travis.yml b/.travis.yml index 40702e8f28b165920fd704ae97af1daa6fab96b6..8c2f28d604556c00c5dcbffae0ce1c7af152fc39 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,15 +1,30 @@ language: cpp before_install: + - sudo add-apt-repository ppa:ubuntu-toolchain-r/test -y - sudo apt-get update -qq +<<<<<<< HEAD - sudo apt-get install -qq libcurl4-openssl-dev libjsoncpp-dev libargtable2-dev libgnutls-dev libgcrypt11-dev valgrind wget - wget ftp://ftp.gnu.org/gnu/libmicrohttpd/libmicrohttpd-0.9.38.tar.gz - tar -xvf libmicrohttpd-0.9.38.tar.gz - cd libmicrohttpd-0.9.38 +======= + - sudo apt-get install -qq libcurl4-openssl-dev libjsoncpp-dev libargtable2-dev libgnutls-dev libgcrypt11-dev valgrind wget gcc-4.8 g++-4.8 + - wget http://ftp.gnu.org/gnu/libmicrohttpd/libmicrohttpd-0.9.44.tar.gz + - tar -xvf libmicrohttpd-0.9.44.tar.gz + - cd libmicrohttpd-0.9.44 +>>>>>>> upstream/0.7.0 - ./configure && make - sudo make install && sudo ldconfig - - cd .. && sudo rm -rf libmicrohttpd-0.9.38 + - 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 env: - HTTP_SERVER=YES HTTP_CLIENT=YES COMPILE_STUBGEN=YES @@ -21,7 +36,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 0d95cd087b4d443f2a26e7002d0733e770b4e3ae..081b7f70a3b1dbbad8040baa6a679c8a74de95aa 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -31,6 +31,7 @@ Marek Kotewicz <marek.kotewicz@gmail.com> Alexandre Poirot <alexandre.poirot@gmail.com> + 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 6358a9a8f34d9147531d65816b9349f8341286e0..d3204857981f49c5e9075f065eed95ce47eaa6b5 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 47993f26d0d9460a72f810fa03bbcaa242ee9f2f..a3cf5e0a78e2726a4507c4f2b7c0980220346757 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) @@ -46,7 +54,7 @@ message(STATUS "COMPILE_STUBGEN: ${COMPILE_STUBGEN}") message(STATUS "COMPILE_EXAMPLES: ${COMPILE_EXAMPLES}") # setup directory where we should look for cmake files -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/") +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake/") # setup compiler settings && dependencies include(CMakeCompilerSettings) @@ -88,7 +96,7 @@ endif() if (DOXYGEN_FOUND) file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/doc) message(STATUS "Found doxygen: ${DOXYGEN_EXECUTABLE}") - configure_file("${CMAKE_SOURCE_DIR}/doc/doxyfile.in" "${CMAKE_BINARY_DIR}/Doxyfile" @ONLY) + configure_file("${PROJECT_SOURCE_DIR}/doc/doxyfile.in" "${CMAKE_BINARY_DIR}/Doxyfile" @ONLY) add_custom_target(doc ${DOXYGEN_EXECUTABLE} ${CMAKE_BINARY_DIR}/Doxyfile WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/doc COMMENT "Generating API documentation") endif(DOXYGEN_FOUND) diff --git a/README.md b/README.md index 458937ac2094622d60a7090eacda56b82b7ef3d5..08fdd5b6df54723295dfdf50580846cea1f2f449 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). @@ -30,7 +30,7 @@ Overview Install the framework ===================== -**Debian** +**Debian (stretch) and Ubuntu (15.10 or later)** ```sh sudo apt-get install libjsonrpccpp-dev libjsonrpccpp-tools @@ -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 ef17165e3117dcb6f13dd2c30d53a283d9e54f14..cca7ebec9ed8e51c187971594e2be95007a3a8c5 100644 --- a/cmake/CMakeCompilerSettings.cmake +++ b/cmake/CMakeCompilerSettings.cmake @@ -1,7 +1,9 @@ # Set necessary compile and link flags + if ("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") - set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Wall -Wextra -Wnon-virtual-dtor -fprofile-arcs -ftest-coverage -fPIC -O0") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") + 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 @@ -9,4 +11,4 @@ elseif ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang") set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Wall -Wextra -Wnon-virtual-dtor -fPIC -O0") elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") # no msvc flags for now -endif() +endif() \ No newline at end of file diff --git a/cmake/CMakeDependencies.cmake b/cmake/CMakeDependencies.cmake index 2749bbdb10360eb0760dd06d09322943830b6011..e283828396b7647ba441301069e4845043708738 100644 --- a/cmake/CMakeDependencies.cmake +++ b/cmake/CMakeDependencies.cmake @@ -43,14 +43,15 @@ endif() # find doxygen find_package(Doxygen) - -find_package(Catch) - -if(NOT CATCH_FOUND) - message("Could not find catch, downloading it now") - # Includes Catch in the project: - add_subdirectory(${CMAKE_SOURCE_DIR}/src/catch) - include_directories(${CATCH_INCLUDE_DIR} ${COMMON_INCLUDES}) -else() - INCLUDE_DIRECTORIES(${CATCH_INCLUDE_DIRS}) +if (${COMPILE_TESTS}) + + find_package(Catch) + if(NOT CATCH_FOUND) + message("Could not find catch, downloading it now") + # Includes Catch in the project: + add_subdirectory(${CMAKE_SOURCE_DIR}/src/catch) + include_directories(${CATCH_INCLUDE_DIR} ${COMMON_INCLUDES}) + else() + INCLUDE_DIRECTORIES(${CATCH_INCLUDE_DIRS}) + endif() endif() diff --git a/dev/ci.sh b/dev/ci.sh index aa967a6e72315a2eddb5fbd3884d57d9f9b3d59c..5b6b1f4449e8b6604c957493b5d18860f0242d54 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 779f42a1ffe9bfcefba53704d50f2e6cc876cb12..e20bd0a26df3652594f3bec31fa8934caac5bbe6 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 e0a99b094860e5c480a8e633d34c0259709f96c8..3b3d81515c940653210f09001eef9b4667b8a5f2 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<Json::Value, Json::Value> 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 0000000000000000000000000000000000000000..98a4a3e0c373b1518798aadc5bbf08d53e5718af --- /dev/null +++ b/src/examples/tcpsocketclient.cpp @@ -0,0 +1,51 @@ +/** + * @file tcpsocketclient.cpp + * @date 17.07.2015 + * @author Alexandre Poirot <alexandre.poirot@legrand.fr> + * @brief tcpsocketclient.cpp + */ + +#include <jsonrpccpp/client.h> +#include <jsonrpccpp/client/connectors/tcpsocketclient.h> +#include <iostream> +#include <cstdlib> + +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 0000000000000000000000000000000000000000..25d1d54cb67b1093bedc20999c0b1730b058d330 --- /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 <stdio.h> +#include <string> +#include <iostream> +#include <jsonrpccpp/server.h> +#include <jsonrpccpp/server/connectors/tcpsocketserver.h> +#include <cstdlib> + + +using namespace jsonrpc; +using namespace std; + +class SampleServer : public AbstractServer<SampleServer> +{ + public: + SampleServer(TcpSocketServer &server) : + AbstractServer<SampleServer>(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 0000000000000000000000000000000000000000..80ac0f40bb35a43a708dee36b37997611c473e6d --- /dev/null +++ b/src/examples/unixdomainsocketclient.cpp @@ -0,0 +1,33 @@ +/** + * @file unixdomainsocketclient.cpp + * @date 11.05.2015 + * @author Alexandre Poirot <alexandre.poirot@legrand.fr> + * @brief unixdomainsocketclient.cpp + */ + +#include <jsonrpccpp/client.h> +#include <jsonrpccpp/client/connectors/unixdomainsocketclient.h> +#include <iostream> + +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 0000000000000000000000000000000000000000..04d694abe6aafeabf722c2cb8d709eee6565e244 --- /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 <stdio.h> +#include <string> +#include <iostream> +#include <jsonrpccpp/server.h> +#include <jsonrpccpp/server/connectors/unixdomainsocketserver.h> + + +using namespace jsonrpc; +using namespace std; + +class SampleServer : public AbstractServer<SampleServer> +{ + public: + SampleServer(UnixDomainSocketServer &server) : + AbstractServer<SampleServer>(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 4c1126d5a373485b1916dbd0e02b6aff15386469..d82cc9aa6453c642bfc1d458c5c938d7d68efb0b 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,21 +48,52 @@ 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 # TODO: move it to custom build step? file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/gen/jsonrpccpp/common") -configure_file("${CMAKE_SOURCE_DIR}/src/jsonrpccpp/version.h.in" "${CMAKE_BINARY_DIR}/gen/jsonrpccpp/version.h") -configure_file("${PROJECT_SOURCE_DIR}/src/jsonrpccpp/common/jsonparser.h.in" "${PROJECT_BINARY_DIR}/gen/jsonrpccpp/common/jsonparser.h") +configure_file("${PROJECT_SOURCE_DIR}/src/jsonrpccpp/version.h.in" "${CMAKE_BINARY_DIR}/gen/jsonrpccpp/version.h") +configure_file("${PROJECT_SOURCE_DIR}/src/jsonrpccpp/common/jsonparser.h.in" "${CMAKE_BINARY_DIR}/gen/jsonrpccpp/common/jsonparser.h") install(FILES "${CMAKE_BINARY_DIR}/gen/jsonrpccpp/version.h" DESTINATION include/jsonrpccpp) install(FILES "${PROJECT_BINARY_DIR}/gen/jsonrpccpp/common/jsonparser.h" DESTINATION include/jsonrpccpp/common) @@ -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 ) @@ -171,14 +203,14 @@ get_filename_component(FULL_PATH_INSTALL_PREFIX ${CMAKE_INSTALL_PREFIX} ABSOLUTE set(FULL_PATH_INCLUDEDIR "${FULL_PATH_INSTALL_PREFIX}/include") set(FULL_PATH_LIBDIR "${FULL_PATH_INSTALL_PREFIX}/lib/${CMAKE_LIBRARY_PATH}") -CONFIGURE_FILE(${CMAKE_SOURCE_DIR}/cmake/libjsonrpccpp-client.pc.cmake ${CMAKE_BINARY_DIR}/libjsonrpccpp-client.pc) -CONFIGURE_FILE(${CMAKE_SOURCE_DIR}/cmake/libjsonrpccpp-server.pc.cmake ${CMAKE_BINARY_DIR}/libjsonrpccpp-server.pc) -CONFIGURE_FILE(${CMAKE_SOURCE_DIR}/cmake/libjsonrpccpp-common.pc.cmake ${CMAKE_BINARY_DIR}/libjsonrpccpp-common.pc) +CONFIGURE_FILE(${PROJECT_SOURCE_DIR}/cmake/libjsonrpccpp-client.pc.cmake ${CMAKE_BINARY_DIR}/libjsonrpccpp-client.pc) +CONFIGURE_FILE(${PROJECT_SOURCE_DIR}/cmake/libjsonrpccpp-server.pc.cmake ${CMAKE_BINARY_DIR}/libjsonrpccpp-server.pc) +CONFIGURE_FILE(${PROJECT_SOURCE_DIR}/cmake/libjsonrpccpp-common.pc.cmake ${CMAKE_BINARY_DIR}/libjsonrpccpp-common.pc) 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 8628fd88e072d7011ea337dd3e20715284a2a78b..6e59d78f8f2217ce3ee6fbeab0a71ea4a7ff406e 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 6480cce00eb29cc52921f0f6ee6f036a73606bb4..fa44be9a530d26608f03cb0f56cb387b87d3c383 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<int, Json::Value> responses; - std::vector<int> errorResponses; + std::map<Json::Value, Json::Value> responses; + std::vector<Json::Value> errorResponses; }; diff --git a/src/jsonrpccpp/client/client.cpp b/src/jsonrpccpp/client/client.cpp index 0f09e64c26f4a8b1176f7fff175cda5a90cac243..7ce965873f6e7713c7b399718c4ade3dac635fd2 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"].isInt()) - 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 0000000000000000000000000000000000000000..631998dde062fff8ddc33781ed8f9caf7a041c91 --- /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 <alexandre.poirot@legrand.fr> + * @license See attached LICENSE.txt + ************************************************************************/ + +#include "linuxtcpsocketclient.h" +#include <string.h> +#include <cstdio> +#include <cstdlib> +#include <unistd.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <netdb.h> +#include <iostream> +#include <errno.h> +#include <cstring> + +#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<size_t>(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<sockaddr_in*>(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 0000000000000000000000000000000000000000..5d05315fc6553cbb630ce4e733b3a909cf14ab85 --- /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 <alexandre.poirot@legrand.fr> + * @license See attached LICENSE.txt + ************************************************************************/ + +#ifndef JSONRPC_CPP_LINUXTCPSOCKETCLIENT_H_ +#define JSONRPC_CPP_LINUXTCPSOCKETCLIENT_H_ + +#include <jsonrpccpp/common/exception.h> +#include <string> +#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 0000000000000000000000000000000000000000..874e0faa7972c8734291c89164e37cade7a09f88 --- /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 <alexandre.poirot@legrand.fr> + * @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 0000000000000000000000000000000000000000..66e80054fe365b0b670106624fdb4685e66c560a --- /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 <alexandre.poirot@legrand.fr> + * @license See attached LICENSE.txt + ************************************************************************/ + +#ifndef JSONRPC_CPP_TCPSOCKETCLIENT_H_ +#define JSONRPC_CPP_TCPSOCKETCLIENT_H_ + +#include "../iclientconnector.h" +#include <jsonrpccpp/common/exception.h> +#include <string> +#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 0000000000000000000000000000000000000000..767eecfe12d9f4f17f24118ad44d3e449edb702a --- /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 <alexandre.poirot@legrand.fr> + * @license See attached LICENSE.txt + ************************************************************************/ + +#ifndef JSONRPC_CPP_TCPSOCKETCLIENTPRIVATE_H_ +#define JSONRPC_CPP_TCPSOCKETCLIENTPRIVATE_H_ + +#include "../iclientconnector.h" +#include <jsonrpccpp/common/exception.h> + +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 1d3dce7b1bdad568d512778b5c86f2fefa6b36e3..585e88d85750cc86d9bb80832c686b15d881fe22 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, this->path.c_str()); + 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<size_t>(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 2a46fde740656fe5049744a926a404e541fa5222..78bfbe282381e2a7491fc8115e9ec54cff70dc61 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 0000000000000000000000000000000000000000..4ebdf6f3984528512f0dee56c8dd1224b2b4c613 --- /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 <alexandre.poirot@legrand.fr> + * @license See attached LICENSE.txt + ************************************************************************/ + +#include "windowstcpsocketclient.h" +#include <string.h> +#include <cstdlib> +#include <iostream> +#undef _WIN32_WINNT +#define _WIN32_WINNT 0x501 +#include <ws2tcpip.h> + +#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<unsigned int>(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<char*>(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<SOCKADDR_IN*>(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<SOCKADDR*>(&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 0000000000000000000000000000000000000000..a1da2e96fc603fbc950e2e4f3739b33350f475b2 --- /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 <alexandre.poirot@legrand.fr> + * @license See attached LICENSE.txt + ************************************************************************/ + +#ifndef JSONRPC_CPP_WINDOWSTCPSOCKETCLIENT_H_ +#define JSONRPC_CPP_WINDOWSTCPSOCKETCLIENT_H_ + +#include <jsonrpccpp/common/exception.h> +#include <string> +#include "tcpsocketclientprivate.h" +#include <winsock2.h> + +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 dfa9c757e73199739afc2b07a63b6dc5b3706aa1..8f95c67fdb2f525cf560df30b7dc6d5ddfe491e0 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) @@ -86,10 +86,20 @@ void RpcProtocolClient::BuildRequest(int id, const std::string &method, const Js void RpcProtocolClient::throwErrorException(const Json::Value &response) { if (response[KEY_ERROR].isMember(KEY_ERROR_MESSAGE) && response[KEY_ERROR][KEY_ERROR_MESSAGE].isString()) + { if (response[KEY_ERROR].isMember(KEY_ERROR_DATA)) + { 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()); - throw JsonRpcException(response[KEY_ERROR][KEY_ERROR_CODE].asInt()); + } + 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()); + } } bool RpcProtocolClient::ValidateResponse(const Json::Value& response) @@ -103,7 +113,7 @@ bool RpcProtocolClient::ValidateResponse(const Json::Value& response) return false; if(!response[KEY_RESULT].isNull() && !response[KEY_ERROR].isNull()) return false; - if (!response[KEY_ERROR].isNull() && !(response[KEY_ERROR].isObject() && response[KEY_ERROR].isMember(KEY_ERROR_CODE) && response[KEY_ERROR][KEY_ERROR_CODE].isInt())) + if (!response[KEY_ERROR].isNull() && !(response[KEY_ERROR].isObject() && response[KEY_ERROR].isMember(KEY_ERROR_CODE) && response[KEY_ERROR][KEY_ERROR_CODE].isIntegral())) return false; } else if (this->version == JSONRPC_CLIENT_V2) @@ -114,7 +124,7 @@ bool RpcProtocolClient::ValidateResponse(const Json::Value& response) return false; if (!response.isMember(KEY_RESULT) && !response.isMember(KEY_ERROR)) return false; - if (response.isMember(KEY_ERROR) && !(response[KEY_ERROR].isObject() && response[KEY_ERROR].isMember(KEY_ERROR_CODE) && response[KEY_ERROR][KEY_ERROR_CODE].isInt())) + if (response.isMember(KEY_ERROR) && !(response[KEY_ERROR].isObject() && response[KEY_ERROR].isMember(KEY_ERROR_CODE) && response[KEY_ERROR][KEY_ERROR_CODE].isIntegral())) return false; } diff --git a/src/jsonrpccpp/client/rpcprotocolclient.h b/src/jsonrpccpp/client/rpcprotocolclient.h index 268f423c1dcc71d2d89f5ca48441cc13f919052b..aa5104bedb4aff0fd4d780e3f89500beb0ac24df 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/common/procedure.cpp b/src/jsonrpccpp/common/procedure.cpp index 0d49ef17f6f625ef9ebaffba70dfe33c13bfb8fe..0d59c5176e0f0d40326d7e785d82a001fb0f5ea9 100644 --- a/src/jsonrpccpp/common/procedure.cpp +++ b/src/jsonrpccpp/common/procedure.cpp @@ -166,7 +166,7 @@ bool Procedure::ValidateSingleParameter (jsontype_t expectedType, const ok = false; break; case JSON_INTEGER: - if (!value.isInt()) + if (!value.isIntegral()) ok = false; break; case JSON_REAL: diff --git a/src/jsonrpccpp/server/abstractprotocolhandler.cpp b/src/jsonrpccpp/server/abstractprotocolhandler.cpp index 617403cd5b6aad628eedad6747624b970e74e012..23df5a7e72027ab3b511dee8535a0ff16d077820 100644 --- a/src/jsonrpccpp/server/abstractprotocolhandler.cpp +++ b/src/jsonrpccpp/server/abstractprotocolhandler.cpp @@ -63,7 +63,7 @@ void AbstractProtocolHandler::ProcessRequest(const Json::Value &request, Json::V else { handler.HandleNotificationCall(method, request[KEY_REQUEST_PARAMETERS]); - response = Json::Value::null; + response = Json::nullValue; } } diff --git a/src/jsonrpccpp/server/connectors/httpserver.cpp b/src/jsonrpccpp/server/connectors/httpserver.cpp index bb494c4507c327619e5390090c4bcb7a75234f2c..5ec15a26ecf2588f3b7c4ac9d727dc546c7fbafd 100644 --- a/src/jsonrpccpp/server/connectors/httpserver.cpp +++ b/src/jsonrpccpp/server/connectors/httpserver.cpp @@ -88,7 +88,7 @@ bool HttpServer::StopListening() bool HttpServer::SendResponse(const string& response, void* addInfo) { struct mhd_coninfo* client_connection = static_cast<struct mhd_coninfo*>(addInfo); - struct MHD_Response *result = MHD_create_response_from_data(response.size(),(void *) response.c_str(), 0, 1); + struct MHD_Response *result = MHD_create_response_from_buffer(response.size(),(void *) response.c_str(), MHD_RESPMEM_MUST_COPY); MHD_add_response_header(result, "Content-Type", "application/json"); MHD_add_response_header(result, "Access-Control-Allow-Origin", "*"); @@ -101,7 +101,7 @@ bool HttpServer::SendResponse(const string& response, void* addInfo) bool HttpServer::SendOptionsResponse(void* addInfo) { struct mhd_coninfo* client_connection = static_cast<struct mhd_coninfo*>(addInfo); - struct MHD_Response *result = MHD_create_response_from_data(0, NULL, 0, 1); + struct MHD_Response *result = MHD_create_response_from_buffer(0, NULL, MHD_RESPMEM_MUST_COPY); MHD_add_response_header(result, "Allow", "POST, OPTIONS"); MHD_add_response_header(result, "Access-Control-Allow-Origin", "*"); @@ -167,6 +167,7 @@ int HttpServer::callback(void *cls, MHD_Connection *connection, const char *url, client_connection->server->SendResponse("Not allowed HTTP Method", client_connection); } delete client_connection; + *con_cls = NULL; return MHD_YES; } diff --git a/src/jsonrpccpp/server/connectors/linuxtcpsocketserver.cpp b/src/jsonrpccpp/server/connectors/linuxtcpsocketserver.cpp new file mode 100644 index 0000000000000000000000000000000000000000..d25edc0497b6c59b16fb638451fb6eb643410a93 --- /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 <alexandre.poirot@legrand.fr> + * @license See attached LICENSE.txt + ************************************************************************/ + +#include "linuxtcpsocketserver.h" + +#include <cstdlib> +#include <cstdio> +#include <cstring> +#include <unistd.h> +#include <sys/types.h> +#include <fcntl.h> + +#include <sstream> +#include <iostream> +#include <string> + +#include <jsonrpccpp/common/specificationparser.h> + +#include <errno.h> + +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<struct sockaddr *>(&(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<bool>(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<intptr_t>(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<LinuxTcpSocketServer*>(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<struct sockaddr *>(&(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<struct GenerateResponseParameters*>(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<void*>(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<size_t>(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 0000000000000000000000000000000000000000..d9461d0bc84ebede490b8aea7f7dc903b45355b2 --- /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 <alexandre.poirot@legrand.fr> + * @license See attached LICENSE.txt + ************************************************************************/ + +#ifndef JSONRPC_CPP_LINUXTCPSOCKETSERVERCONNECTOR_H_ +#define JSONRPC_CPP_LINUXTCPSOCKETSERVERCONNECTOR_H_ + +#include <stdarg.h> +#include <stdint.h> +#include <sys/types.h> +#include <unistd.h> +#include <sys/time.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <pthread.h> + +#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 0000000000000000000000000000000000000000..e6c23c79f78c8c2f7ecb649e4d00c5ba1b9f8b8f --- /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 <alexandre.poirot@legrand.fr> + * @license See attached LICENSE.txt + ************************************************************************/ + +#include "tcpsocketserver.h" +#ifdef __WIN32__ +#include "windowstcpsocketserver.h" +#elif __unix__ +#include "linuxtcpsocketserver.h" +#endif +#include <string> + +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 0000000000000000000000000000000000000000..2c9c69f7e92461f2bbd04ef3bb24447a167bab1f --- /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 <alexandre.poirot@legrand.fr> + * @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 2f1cdb6cab91b561242fdd586b65b9bb8dbf248c..9fca57b6b212619eded01567d94f4f9071ab9fb5 100644 --- a/src/jsonrpccpp/server/connectors/unixdomainsocketserver.cpp +++ b/src/jsonrpccpp/server/connectors/unixdomainsocketserver.cpp @@ -10,6 +10,7 @@ #include "unixdomainsocketserver.h" #include <cstdlib> #include <sstream> +#include <iostream> #include <sys/types.h> #include <jsonrpccpp/common/specificationparser.h> #include <cstdio> @@ -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, this->socket_path.c_str()); + snprintf(this->address.sun_path, PATH_MAX, "%s", this->socket_path.c_str()); - bind(this->socket_fd, reinterpret_cast<struct sockaddr *>(&(this->address)), sizeof(struct sockaddr_un)); + if(bind(this->socket_fd, reinterpret_cast<struct sockaddr *>(&(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<bool>(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<intptr_t>(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<UnixDomainSocketServer*>(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<struct sockaddr *>(&(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<struct ClientConnection*>(p_data); +void* UnixDomainSocketServer::GenerateResponse(void *p_data) +{ + pthread_detach(pthread_self()); + struct ClientConnection* params = reinterpret_cast<struct ClientConnection*>(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<void*>(connection_fd)); - pthread_exit(NULL); - return NULL; + instance->OnRequest(request, reinterpret_cast<void*>(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<ssize_t>(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 e92f02a654997792f526c8e591828cd0a6c309a6..2d2072b03b9cd4e4a8249537f5deca096cc6a813 100644 --- a/src/jsonrpccpp/server/connectors/unixdomainsocketserver.h +++ b/src/jsonrpccpp/server/connectors/unixdomainsocketserver.h @@ -18,7 +18,6 @@ #include <sys/socket.h> #include <sys/un.h> #include <pthread.h> -#include <set> #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<pthread_t> 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 0000000000000000000000000000000000000000..95dcbc2d1c22094653f2c78436c8b9138ceeda23 --- /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 <alexandre.poirot@legrand.fr> + * @license See attached LICENSE.txt + ************************************************************************/ + +#include "windowstcpsocketserver.h" + +#include <cstdlib> +#include <cstdio> +#include <cstring> +#include <windows.h> + +#include <sstream> +#include <iostream> +#include <string> + +#include <jsonrpccpp/common/specificationparser.h> + +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<char*>(&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<SOCKADDR*>(&(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<LPTHREAD_START_ROUTINE>(&(WindowsTcpSocketServer::LaunchLoop)), reinterpret_cast<LPVOID>(this), 0, &(this->listenning_thread)); + if(ret == NULL) + { + ExitProcess(3); + } + else + { + CloseHandle(ret); + } + this->running = static_cast<bool>(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<intptr_t>(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<WindowsTcpSocketServer*>(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<SOCKADDR*>(&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<LPTHREAD_START_ROUTINE>(&(WindowsTcpSocketServer::GenerateResponse)), reinterpret_cast<LPVOID>(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<struct GenerateResponseParameters*>(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<void*>(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<char*>(&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 0000000000000000000000000000000000000000..88f0e80e963f8e5943869d4e379b5f2d00bfc96b --- /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 <alexandre.poirot@legrand.fr> + * @license See attached LICENSE.txt + ************************************************************************/ + +#ifndef JSONRPC_CPP_WINDOWSTCPSOCKETSERVERCONNECTOR_H_ +#define JSONRPC_CPP_WINDOWSTCPSOCKETSERVERCONNECTOR_H_ + +#include <stdarg.h> +#include <stdint.h> +#include <winsock2.h> + +#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/jsonrpccpp/server/rpcprotocolserverv2.cpp b/src/jsonrpccpp/server/rpcprotocolserverv2.cpp index 564b61698e61509a07b33c3d5223809fb3f071c3..2ab631c13dde35638ef5552770385dd71d6cb45f 100644 --- a/src/jsonrpccpp/server/rpcprotocolserverv2.cpp +++ b/src/jsonrpccpp/server/rpcprotocolserverv2.cpp @@ -77,9 +77,9 @@ bool RpcProtocolServerV2::ValidateRequestFields(const Json::Value &request) return false; if (!(request.isMember(KEY_REQUEST_VERSION) && request[KEY_REQUEST_VERSION].isString() && request[KEY_REQUEST_VERSION].asString() == JSON_RPC_VERSION2)) return false; - if (request.isMember(KEY_REQUEST_ID) && !(request[KEY_REQUEST_ID].isInt() || request[KEY_REQUEST_ID].isString() || request[KEY_REQUEST_ID].isNull())) + if (request.isMember(KEY_REQUEST_ID) && !(request[KEY_REQUEST_ID].isIntegral() || request[KEY_REQUEST_ID].isString() || request[KEY_REQUEST_ID].isNull())) return false; - if (request.isMember(KEY_REQUEST_PARAMETERS) && !(request[KEY_REQUEST_PARAMETERS].isObject() || request[KEY_REQUEST_PARAMETERS].isArray() || request[KEY_REQUEST_ID].isNull())) + if (request.isMember(KEY_REQUEST_PARAMETERS) && !(request[KEY_REQUEST_PARAMETERS].isObject() || request[KEY_REQUEST_PARAMETERS].isArray() || request[KEY_REQUEST_PARAMETERS].isNull())) return false; return true; } @@ -97,7 +97,7 @@ void RpcProtocolServerV2::WrapError(const Json::Value &request, int code, const result["error"]["code"] = code; result["error"]["message"] = message; - if(request.isObject() && request.isMember("id") && (request["id"].isNull() || request["id"].isInt() || request["id"].isUInt() || request["id"].isString())) + if(request.isObject() && request.isMember("id") && (request["id"].isNull() || request["id"].isIntegral() || request["id"].isString())) { result["id"] = request["id"]; } diff --git a/src/stubgenerator/CMakeLists.txt b/src/stubgenerator/CMakeLists.txt index a5e55647964ffbda829635bde680f7d56eabd221..f9dbe4c44a18e9b2c98bd51d1bdf354422038d6d 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 2dc60353d255f35aab99933818ff80937c5d4d23..7900eb0fce95d3677ad2233ef4c9ec28d47b219c 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/stubgenerator/helper/cpphelper.cpp b/src/stubgenerator/helper/cpphelper.cpp index 916ad5eb8ee20233193faefb922deb6b1b3cc02b..020062edabdb40f2d3d7e5ec936a2e1b00fb5356 100755 --- a/src/stubgenerator/helper/cpphelper.cpp +++ b/src/stubgenerator/helper/cpphelper.cpp @@ -219,7 +219,7 @@ string CPPHelper::isCppConversion (jsontype_t type) result = ".isBool()"; break; case JSON_INTEGER: - result = ".isInt()"; + result = ".isIntegral()"; break; case JSON_REAL: result = ".isDouble()"; diff --git a/src/stubgenerator/server/cppserverstubgenerator.h b/src/stubgenerator/server/cppserverstubgenerator.h index bac2d52e2b82929c56f816e187405af7642a3826..1351155c4feee8c8c0e69ef309716c520a17b593 100755 --- a/src/stubgenerator/server/cppserverstubgenerator.h +++ b/src/stubgenerator/server/cppserverstubgenerator.h @@ -23,7 +23,6 @@ namespace jsonrpc virtual void generateStub(); - private: void generateBindings(); void generateProcedureDefinitions(); void generateAbstractDefinitions(); diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt index 088f59504dc3548ff9a92cfdba50134cdbff9ce9..9438134f4c19fc4d2781285d13a3ad395477ff5a 100644 --- a/src/test/CMakeLists.txt +++ b/src/test/CMakeLists.txt @@ -1,20 +1,34 @@ set(CTEST_OUTPUT_ON_FAILURE TRUE) - file(GLOB_RECURSE test_source *.cpp) -file(GLOB test_specs *.json) - -file(COPY ${CMAKE_SOURCE_DIR}/src/examples/server.key ${CMAKE_SOURCE_DIR}/src/examples/server.pem DESTINATION ${CMAKE_BINARY_DIR}) -file(COPY ${CMAKE_SOURCE_DIR}/src/examples/server.key ${CMAKE_SOURCE_DIR}/src/examples/server.pem DESTINATION ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}) -file(COPY ${test_specs} DESTINATION ${CMAKE_BINARY_DIR}) -file(COPY ${test_specs} DESTINATION ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}) include_directories(..) include_directories(${CMAKE_BINARY_DIR}) include_directories(${JSONCPP_INCLUDE_DIRS}) +add_custom_command( + OUTPUT ${CMAKE_BINARY_DIR}/gen/abstractstubserver.h + COMMAND jsonrpcstub ARGS ${CMAKE_CURRENT_SOURCE_DIR}/spec.json --cpp-server=AbstractStubServer --cpp-server-file=${CMAKE_BINARY_DIR}/gen/abstractstubserver.h + MAIN_DEPENDENCY spec.json + DEPENDS jsonrpcstub + COMMENT "Generating Server Stubfiles" + VERBATIM +) + +add_custom_command( + OUTPUT ${CMAKE_BINARY_DIR}/gen/stubclient.h + COMMAND jsonrpcstub ARGS ${CMAKE_CURRENT_SOURCE_DIR}/spec.json --cpp-client=StubClient --cpp-client-file=${CMAKE_BINARY_DIR}/gen/stubclient.h + MAIN_DEPENDENCY spec.json + DEPENDS jsonrpcstub + COMMENT "Generating Client Stubfile" + VERBATIM +) + + if(HTTP_CLIENT AND HTTP_SERVER) add_definitions(-DHTTP_TESTING) + file(COPY ${CMAKE_SOURCE_DIR}/src/examples/server.key ${CMAKE_SOURCE_DIR}/src/examples/server.pem DESTINATION ${CMAKE_BINARY_DIR}) + file(COPY ${CMAKE_SOURCE_DIR}/src/examples/server.key ${CMAKE_SOURCE_DIR}/src/examples/server.pem DESTINATION ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}) else() list(REMOVE_ITEM test_source "${CMAKE_CURRENT_SOURCE_DIR}/testhttpserver.cpp") endif() @@ -23,8 +37,17 @@ 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) + file(COPY ${test_specs} DESTINATION ${CMAKE_BINARY_DIR}) + file(COPY ${test_specs} DESTINATION ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}) + list(APPEND test_source "${CMAKE_BINARY_DIR}/gen/abstractstubserver.h") + list(APPEND test_source "${CMAKE_BINARY_DIR}/gen/stubclient.h") endif() add_executable(unit_testsuite ${test_source}) @@ -32,11 +55,14 @@ target_link_libraries(unit_testsuite jsonrpccommon) target_link_libraries(unit_testsuite jsonrpcserver) target_link_libraries(unit_testsuite jsonrpcclient) +if (NOT CATCH_FOUND) + # let's wait for catch files to be downloaded + add_dependencies(unit_testsuite catch) +endif() + if(COMPILE_STUBGEN) - list(APPEND test_source "${CMAKE_BINARY_DIR}/gen/abstractstubserver.h") - list(APPEND test_source "${CMAKE_BINARY_DIR}/gen/stubclient.h") target_link_libraries(unit_testsuite libjsonrpcstub) - add_dependencies(unit_testsuite common_stubs) + add_dependencies(unit_testsuite libjsonrpcstub) endif() add_test(client ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/unit_testsuite "[client]") @@ -51,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/spec.json b/src/test/spec.json new file mode 100644 index 0000000000000000000000000000000000000000..2eaa32e47c175cb5c085b34e159094feadd18041 --- /dev/null +++ b/src/test/spec.json @@ -0,0 +1,51 @@ +[ + { + "name": "sayHello", + "params": { + "name": "Peter" + }, + "returns": "Hello Peter" + }, + { + "name": "notifyServer" + }, + { + "name": "addNumbers", + "params": [ + 3, + 4 + ], + "returns": 7 + }, + { + "name": "addNumbers2", + "params": [ + 3.2, + 4.1 + ], + "returns": 7.5 + }, + { + "name": "isEqual", + "params": [ + "string1", + "string2" + ], + "returns": false + }, + { + "name": "buildObject", + "params": [ + "peter", + 1990 + ], + "returns": { + "name": "peter", + "year": 1990 + } + }, + { + "name" : "methodWithoutParameters", + "returns": "String" + } +] diff --git a/src/test/test_client.cpp b/src/test/test_client.cpp index 46eb60f0db21be962a16191bc2870fd0cfadf0a9..0676825ad8a04b8996321aec182c56c90a2ee09e 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_common.cpp b/src/test/test_common.cpp index 67e5249ced39453806a17dc6b65cf7beabec0842..03de32dbaf507ec5c66cd4aee3135aecf3dc4f56 100644 --- a/src/test/test_common.cpp +++ b/src/test/test_common.cpp @@ -174,7 +174,7 @@ TEST_CASE("test_specificationwriter", TEST_MODULE) CHECK(result[4]["name"].asString() == "testnotification3"); REQUIRE(result[0]["params"].isObject() == true); - CHECK(result[0]["params"]["param1"].isInt() == true); + CHECK(result[0]["params"]["param1"].isIntegral() == true); CHECK(result[0]["params"]["param2"].isDouble() == true); REQUIRE(result[1]["params"].isArray() == true); @@ -186,13 +186,13 @@ TEST_CASE("test_specificationwriter", TEST_MODULE) CHECK(result[2]["params"]["param2"].isString() == true); REQUIRE(result[3]["params"].isArray() == true); - CHECK(result[3]["params"][0].isInt() == true); + CHECK(result[3]["params"][0].isIntegral() == true); CHECK(result[3]["params"][1].isString() == true); CHECK(result[4].isMember("params") == false); - CHECK(result[0]["returns"].isInt() == true); - CHECK(result[1]["returns"].isInt() == true); + CHECK(result[0]["returns"].isIntegral() == true); + CHECK(result[1]["returns"].isIntegral() == true); CHECK(SpecificationWriter::toFile("testspec.json", procedures) == true); CHECK(SpecificationWriter::toFile("/a/b/c/testspec.json", procedures) == false); diff --git a/src/test/test_connector_tcpsocket.cpp b/src/test/test_connector_tcpsocket.cpp new file mode 100644 index 0000000000000000000000000000000000000000..920077bf1e51c327de34f9d283646829f58fde8a --- /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 <alexandre.poirot@legrand.fr> + * @license See attached LICENSE.txt + ************************************************************************/ + +#ifdef TCPSOCKET_TESTING +#include <catch.hpp> +#include <jsonrpccpp/server/connectors/tcpsocketserver.h> +#include <jsonrpccpp/client/connectors/tcpsocketclient.h> +#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 561fec39e5ff2911a546aeef448dcf9854cd10e8..bcd27108abc9e6c887de100bbccb5d7ba919c5a9 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); } diff --git a/src/test/test_server.cpp b/src/test/test_server.cpp index 1507a3b7ad42dd55b6d058240d485e1b60ceb399..3e1c92cfac801ea7ae3ead63a5e9878c5d159269 100644 --- a/src/test/test_server.cpp +++ b/src/test/test_server.cpp @@ -65,6 +65,13 @@ TEST_CASE_METHOD(F, "test_server_v2_method_success", TEST_MODULE) CHECK(c.GetJsonResponse()["id"].asString() == "1"); CHECK(c.GetJsonResponse()["jsonrpc"].asString() == "2.0"); CHECK(c.GetJsonResponse().isMember("error") == false); + + c.SetRequest("{\"jsonrpc\":\"2.0\", \"id\": 4294967295, \"method\": \"sub\",\"params\":[5,7]}"); + CHECK(c.GetJsonResponse()["result"].asInt() == -2); + CHECK(c.GetJsonResponse()["id"].asLargestUInt() == (unsigned long)4294967295); + CHECK(c.GetJsonResponse()["jsonrpc"].asString() == "2.0"); + CHECK(c.GetJsonResponse().isMember("error") == false); + } TEST_CASE_METHOD(F, "test_server_v2_notification_success", TEST_MODULE) diff --git a/src/test/testhttpserver.cpp b/src/test/testhttpserver.cpp index f8802453deabc57d4e9fc99a518b2cff087794a0..7e240a67e149552948fbdfb00a6b4236ed8f3d4c 100644 --- a/src/test/testhttpserver.cpp +++ b/src/test/testhttpserver.cpp @@ -63,7 +63,7 @@ int TestHttpServer::callback(void *cls, MHD_Connection *connection, const char * else { MHD_get_connection_values(connection, MHD_HEADER_KIND, header_iterator, cls); - struct MHD_Response *result = MHD_create_response_from_data(_this->response.size(),(void *) _this->response.c_str(), 0, 1); + struct MHD_Response *result = MHD_create_response_from_buffer(_this->response.size(),(void *) _this->response.c_str(), MHD_RESPMEM_MUST_COPY); MHD_add_response_header(result, "Content-Type", "application/json"); MHD_add_response_header(result, "Access-Control-Allow-Origin", "*"); MHD_queue_response(connection, MHD_HTTP_OK, result);