diff --git a/.gitignore b/.gitignore index 514756f..258e0d7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,41 +1,12 @@ -stamp-h1 -eixx*.tar.gz -aclocal.m4 -autom4te.cache/ -build-aux/config.guess -build-aux/config.sub -build-aux/depcomp -build-aux/install-sh -build-aux/libtool.m4 -build-aux/ltmain.sh -build-aux/ltoptions.m4 -build-aux/ltsugar.m4 -build-aux/ltversion.m4 -build-aux/lt~obsolete.m4 -build-aux/missing -_configs.sed -/config.h -config.h.in* -config.log -config.status -configure -libtool -Makefile -Makefile.in -src/.deps -src/.libs -src/Makefile -src/Makefile.in -src/eixx.pc -src/pri_test -src/test_node -src/*.la -src/*.lo -src/*.o -test/.deps -test/.libs -test/Makefile -test/Makefile.in -test/test_eterm -test/*.o -test/test_perf +*.swp +*.dump +.build/ +.cproject +.kdev4/ +.project +.cmake-args* +build +inst +test/.kdev4/ +test/test.kdev4 + diff --git a/.kdev_include_paths b/.kdev_include_paths new file mode 100644 index 0000000..ec20600 --- /dev/null +++ b/.kdev_include_paths @@ -0,0 +1,3 @@ +include +/opt/erlang/lib/erlang/lib/erl_interface-3.7.11/include +/opt/boost/include diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..80c732a --- /dev/null +++ b/.travis.yml @@ -0,0 +1,52 @@ +language: cpp +compiler: g++ +language: erlang +otp_release: + - 23.0 + +matrix: + include: + - os: linux + dist: bionic # Required for gcc-10 + addons: + apt: + update: true + sources: + - sourceline: 'ppa:mhier/libboost-latest' + #- sourceline: 'ppa:ubuntu-toolchain-r/test' + - ubuntu-toolchain-r-test + packages: + - gcc-10 + - g++-10 + #- boost-latest + - boost1.74 + - libxslt1.1 + - python-lxml + - doxygen + env: + - MATRIX_EVAL="CC=gcc-10 && CXX=g++-10" + +before_install: + - eval "${MATRIX_EVAL}" + +before_script: + - echo "DIR:BUILD=/tmp/${USER}/utxx" > .cmake-args.$(hostname) + - echo "DIR:INSTALL=/tmp/${USER}/install/@PROJECT@/@VERSION@" >> .cmake-args.$(hostname) + - echo "PKG_ROOT_DIR=/usr/local" >> .cmake-args.$(hostname) + - echo "BOOST_INCLUDEDIR=/usr/include" >> .cmake-args.$(hostname) + - echo "BOOST_LIBRARYDIR=/usr/lib/x86_64-linux-gnu" >> .cmake-args.$(hostname) + - echo "CMAKE_CXX_COMPILER=${CXX}" >> .cmake-args.$(hostname) + +script: + - ${CXX} --version + ############################################################################ + # Install a recent CMake (unless already installed on OS X) + ############################################################################ + - cd ${TRAVIS_BUILD_DIR} # Go back to the root of the project and bootstrap + - make bootstrap generator=make build=Debug + - make + - make doc > build/doc.log + - make install + - make test +branches: + only: master diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..6913c39 --- /dev/null +++ b/.vscode/c_cpp_properties.json @@ -0,0 +1,20 @@ +{ + "configurations": [ + { + "name": "Linux", + "includePath": [ + "${workspaceFolder}/include", + "/opt/pkg/boost/current/include", + "/opt/sw/erlang/current/lib/erlang/lib/erl_interface-4.0/include" + ], + "defines": [], + "compilerPath": "/usr/bin/clang", + "cStandard": "c18", + "cppStandard": "c++17", + "intelliSenseMode": "clang-x64", + "compileCommands": "${workspaceFolder}/build/compile_commands.json", + "configurationProvider": "ms-vscode.cmake-tools" + } + ], + "version": 4 +} \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..4d95674 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,41 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "test-eterm", + "type": "cppdbg", + "request": "launch", + "program": "${workspaceFolder}/build/test/test-eterm", + "args": ["-t", "test_map"], + "stopAtEntry": false, + "cwd": "${workspaceFolder}", + "environment": [], + "externalConsole": false, + "MIMode": "gdb", + "setupCommands": [ + { + "description": "Enable pretty-printing for gdb", + "text": "-enable-pretty-printing", + "ignoreFailures": true + } + ], + //"preLaunchTask": "C/C++: g++ build active file", + "miDebuggerPath": "/usr/bin/gdb" + }, + { + "name": "test-node", + "type": "cppdbg", + "request": "launch", + "program": "${workspaceFolder}/build/src/test-node", + "args": ["-n", "test-node@127.0.0.1", "-r", "iex-demo1@127.0.0.1", "-v", "trace"], + "stopAtEntry": false, + "cwd": "${workspaceFolder}", + "environment": [], + "externalConsole": false, + "MIMode": "lldb" + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..0878f6d --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,34 @@ +{ + "files.associations": { + "initializer_list": "cpp", + "valarray": "cpp", + "array": "cpp", + "atomic": "cpp", + "chrono": "cpp", + "list": "cpp", + "vector": "cpp", + "exception": "cpp", + "functional": "cpp", + "iosfwd": "cpp", + "istream": "cpp", + "mutex": "cpp", + "new": "cpp", + "ostream": "cpp", + "ratio": "cpp", + "sstream": "cpp", + "stdexcept": "cpp", + "tuple": "cpp", + "type_traits": "cpp", + "utility": "cpp" + }, + "cmake.configureSettings": { + "TOOLCHAIN" : "gcc", + "CMAKE_USER_MAKE_RULES_OVERRIDE" : "${workspaceFolder}/build-aux/CMakeInit.txt", + "CMAKE_INSTALL_PREFIX" : "/opt/pkg/eixx/1.4" + }, + "cmake.buildArgs": [ + ], + "cmake.preferredGenerators": [ + "Unix Makefiles" + ] +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..6f87cd5 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,26 @@ +{ + "tasks": [ + { + "type": "shell", + "label": "C/C++: g++ build active file", + "command": "/usr/bin/g++", + "args": [ + "-g", + "${file}", + "-o", + "${fileDirname}/${fileBasenameNoExtension}" + ], + "options": { + "cwd": "${workspaceFolder}" + }, + "problemMatcher": [ + "$gcc" + ], + "group": { + "kind": "build", + "isDefault": true + } + } + ], + "version": "2.0.0" +} \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..a1dcd09 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,243 @@ +# vim:ts=2:sw=2:et +cmake_minimum_required(VERSION 3.1.0 FATAL_ERROR) + +# Overrides - must be placed before 'project' +set(CMAKE_USER_MAKE_RULES_OVERRIDE "${CMAKE_SOURCE_DIR}/build-aux/CMakeInit.txt") + +project(eixx VERSION 1.4) + +#=============================================================================== +# CMAKE options customization +#=============================================================================== +option(VERBOSE "Turn verbosity on|off" OFF) +option(EIXX_MARSHAL_ONLY "Limit build to eterm marshal lib on|off" OFF) + +if(VERBOSE) + set(CMAKE_VERBOSE_MAKEFILE ON) +endif() +if(WITH_ENUM_SERIALIZATION) + set(UTXX_ENUM_SUPPORT_SERIALIZATION ON) +endif() + +string(TOLOWER ${TOOLCHAIN} toolchain) +string(TOLOWER "${CMAKE_BUILD_TYPE}" CMAKE_BUILD_TYPE) + +# Custom extensions +include(${CMAKE_CURRENT_SOURCE_DIR}/build-aux/CMakeEx.txt) + +#------------------------------------------------------------------------------- +# Toolchain +#------------------------------------------------------------------------------- +# See also build/CMakeInit.txt +if("${toolchain}" STREQUAL "gcc") + if(NOT CMAKE_C_COMPILER) + set(CMAKE_C_COMPILER "gcc") + endif() + if(NOT CMAKE_CXX_COMPILER) + set(CMAKE_CXX_COMPILER "g++") + endif() + add_definitions(-fopenmp -Wall -Wextra -Wpedantic -Wshadow -Wno-strict-aliasing -Wconversion -Wsign-conversion) + + if("${CMAKE_BUILD_TYPE}" STREQUAL "release") + add_definitions(-flto -funroll-loops -fomit-frame-pointer) + + # The following will omit all symbol information from the build: + #add_definitions(-Wl,-s) + #set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s") + endif() +elseif("${toolchain}" STREQUAL "intel") + set(CMAKE_C_COMPILER "icc") + set(CMAKE_CXX_COMPILER "icpc") + add_definitions(-openmp) + +elseif("${toolchain}" STREQUAL "clang") + set(CMAKE_C_COMPILER "clang") + set(CMAKE_CXX_COMPILER "clang++") + add_definitions(-Wall -Wextra -Wpedantic -Wshadow -Wno-strict-aliasing -Wconversion -Wsign-conversion) +else() + message(FATAL_ERROR "Invalid toolchain: ${TOOLCHAIN}") +endif() + +# Append "_d" to the library when doing debug build +if (CMAKE_BUILD_TYPE STREQUAL "debug") + set(LIB_SUFFIX "_d") +endif() + +# Note: explicit c++14 definitions done in CMakeInit.txt. +# Alternative is to set for each target: +# target_compile_features(${PROJECT_NAME} PRIVATE cxx_lambda_init_captures) + +add_definitions( + -D_REENTRANT + -Wno-unused-local-typedefs + -Wno-deprecated-declarations + -Wpedantic + -DBOOST_SYSTEM_NO_DEPRECATED + -DBOOST_BIND_GLOBAL_PLACEHOLDERS +) + +message(STATUS "Configuring for the " + "${BoldMagenta}${TOOLCHAIN}${ClrReset} toolchain " + "${BoldMagenta}${CMAKE_BUILD_TYPE}${ClrReset} build") + +#------------------------------------------------------------------------------- +# Policies +#------------------------------------------------------------------------------- + +# RPATH configuration +# =================== +# Don't skip the full RPATH for the build tree +set(CMAKE_SKIP_BUILD_RPATH FALSE) +# When building, don't use the install RPATH already +# (but later on when installing) +set(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE) +set(CMAKE_INSTALL_RPATH "${CMAKE_BINARY_DIR}/src:${CMAKE_INSTALL_PREFIX}/lib") +# Add the automatically determined parts of the RPATH +# which point to directories outside the build tree to the install RPATH +set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) + +#------------------------------------------------------------------------------- +# Custom cmake libs +#------------------------------------------------------------------------------- +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/build-aux) + +#include(${CMAKE_ROOT}/Modules/CheckTypeSize.cmake) +#include(${CMAKE_ROOT}/Modules/CheckFunctionExists.cmake) +#include(${CMAKE_ROOT}/Modules/CheckStructHasMember.cmake) +include(${CMAKE_ROOT}/Modules/CheckIncludeFile.cmake) +include(${CMAKE_SOURCE_DIR}/build-aux/AlignOf.cmake) +#include(${CMAKE_SOURCE_DIR}/build-aux/FindErlang.cmake) + +#------------------------------------------------------------------------------- +# Dependent packages and their directory locations +#------------------------------------------------------------------------------- +find_package(PkgConfig) +find_package(OpenSSL REQUIRED) +find_package(Erlang REQUIRED) + +set(PKG_ROOT_DIR "/opt/pkg" CACHE STRING "Package root directory") + +# Boost (with local modifications): +set(Boost_USE_STATIC_LIBS OFF) +set(Boost_USE_MULTITHREAD ON) +set(Boost_NO_SYSTEM_PATHS ON) +set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/build") + +find_package(Boost 1.55.0 REQUIRED COMPONENTS system thread) + +if(Boost_FOUND) + #include_directories(SYSTEM ${Boost_INCLUDE_DIRS}) + #link_directories(${Boost_LIBRARY_DIRS}) + set(UTXX_HAVE_BOOST_TIMER_TIMER_HPP 1) + message(STATUS "Found boost: ${Boost_LIBRARY_DIRS}") +endif() + +#------------------------------------------------------------------------------- +# Platform-specific checks +#------------------------------------------------------------------------------- +set(CMAKE_REQUIRED_INCLUDES ${ERLANG_EI_PATH}/src) +CHECK_INCLUDE_FILE(epmd/ei_epmd.h HAVE_EI_EPMD) +unset(CMAKE_REQUIRED_INCLUDES) +ALIGNOF(uint64_t cpp UINT64_T) + +#------------------------------------------------------------------------------- +# MAKE options +#------------------------------------------------------------------------------- + +include_directories( + SYSTEM + ${Boost_INCLUDE_DIRS} + ${OPENSSL_INCLUDE_DIR} + ${Erlang_EI_INCLUDE_DIR} + ${Erlang_EI_DIR}/src +) +include_directories( + ${CMAKE_SOURCE_DIR}/include + ${CMAKE_BINARY_DIR}/include +) +link_directories( + ${Boost_LIBRARY_DIRS} + ${Erlang_EI_LIBRARY_DIR} +) + +set(EIXX_LIBS + ${Erlang_EI_LIBRARIES} + ${OPENSSL_LIBRARIES} # For MD5 support + pthread +) + +#------------------------------------------------------------------------------- +# Configure files +#------------------------------------------------------------------------------- +configure_file("${CMAKE_CURRENT_SOURCE_DIR}/config.h.in" + "${CMAKE_CURRENT_BINARY_DIR}/include/${PROJECT_NAME}/config.h" + @ONLY) +configure_file("${CMAKE_CURRENT_SOURCE_DIR}/${PROJECT_NAME}.pc.in" + "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.pc" @ONLY) +#------------------------------------------------------------------------------- +# Srcs and Targets: +#------------------------------------------------------------------------------- + +add_subdirectory(${CMAKE_SOURCE_DIR}/src) +add_subdirectory(${CMAKE_SOURCE_DIR}/test) + +#=============================================================================== +# Installation +#=============================================================================== +install( + DIRECTORY ${CMAKE_SOURCE_DIR}/include/${PROJECT_NAME} + DESTINATION ${CMAKE_INSTALL_PREFIX}/include + FILES_MATCHING PATTERN "*.h" PATTERN "*.hpp" PATTERN "*.hxx" +) +install( + FILES ${CMAKE_CURRENT_BINARY_DIR}/include/${PROJECT_NAME}/config.h + DESTINATION ${CMAKE_INSTALL_PREFIX}/include/${PROJECT_NAME} +) +install( + FILES ${CMAKE_BINARY_DIR}/${PROJECT_NAME}.pc + DESTINATION ${CMAKE_INSTALL_PREFIX}/lib/pkgconfig +) +install( + FILES ${CMAKE_SOURCE_DIR}/LICENSE + ${CMAKE_SOURCE_DIR}/README.md + DESTINATION ${CMAKE_INSTALL_PREFIX}/share +) + +#=============================================================================== +# Uninstallation +# Prereq: copy the uninstall.cmake file to the appropriate CMAKE_MODULE_PATH. +#=============================================================================== +set_directory_properties( + PROPERTIES ADDITIONAL_MAKE_CLEAN_FILES "${AddCleanFiles}" +) + +#add_custom_target( +# uninstall "${CMAKE_COMMAND}" -P "${CMAKE_MODULE_PATH}/uninstall.cmake" +#) + +#=============================================================================== +# CTEST options +#=============================================================================== +enable_testing() + +add_test(test-eixx test/test-eterm -l message) + +#=============================================================================== +# Documentation options +#=============================================================================== +# add a target to generate API documentation with Doxygen +find_package(Doxygen) +if(DOXYGEN_FOUND) + configure_file(${CMAKE_CURRENT_SOURCE_DIR}/build-aux/Doxyfile.in + ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile + @ONLY) + add_custom_target(doc + ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + COMMENT "${ClrBold}Generating API documentation with Doxygen${ClrReset}" + VERBATIM + ) +endif() + +# Post-install script (installation of symlinks): +install(SCRIPT ${CMAKE_SOURCE_DIR}/build-aux/install-symlinks.cmake) diff --git a/LICENSE b/LICENSE index 4362b49..e454a52 100644 --- a/LICENSE +++ b/LICENSE @@ -1,502 +1,178 @@ - GNU LESSER GENERAL PUBLIC LICENSE - Version 2.1, February 1999 - Copyright (C) 1991, 1999 Free Software Foundation, Inc. - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS -[This is the first released version of the Lesser GPL. It also counts - as the successor of the GNU Library Public License, version 2, hence - the version number 2.1.] - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -Licenses are intended to guarantee your freedom to share and change -free software--to make sure the software is free for all its users. - - This license, the Lesser General Public License, applies to some -specially designated software packages--typically libraries--of the -Free Software Foundation and other authors who decide to use it. You -can use it too, but we suggest you first think carefully about whether -this license or the ordinary General Public License is the better -strategy to use in any particular case, based on the explanations below. - - When we speak of free software, we are referring to freedom of use, -not price. Our General Public Licenses are designed to make sure that -you have the freedom to distribute copies of free software (and charge -for this service if you wish); that you receive source code or can get -it if you want it; that you can change the software and use pieces of -it in new free programs; and that you are informed that you can do -these things. - - To protect your rights, we need to make restrictions that forbid -distributors to deny you these rights or to ask you to surrender these -rights. These restrictions translate to certain responsibilities for -you if you distribute copies of the library or if you modify it. - - For example, if you distribute copies of the library, whether gratis -or for a fee, you must give the recipients all the rights that we gave -you. You must make sure that they, too, receive or can get the source -code. If you link other code with the library, you must provide -complete object files to the recipients, so that they can relink them -with the library after making changes to the library and recompiling -it. And you must show them these terms so they know their rights. - - We protect your rights with a two-step method: (1) we copyright the -library, and (2) we offer you this license, which gives you legal -permission to copy, distribute and/or modify the library. - - To protect each distributor, we want to make it very clear that -there is no warranty for the free library. Also, if the library is -modified by someone else and passed on, the recipients should know -that what they have is not the original version, so that the original -author's reputation will not be affected by problems that might be -introduced by others. - - Finally, software patents pose a constant threat to the existence of -any free program. We wish to make sure that a company cannot -effectively restrict the users of a free program by obtaining a -restrictive license from a patent holder. Therefore, we insist that -any patent license obtained for a version of the library must be -consistent with the full freedom of use specified in this license. - - Most GNU software, including some libraries, is covered by the -ordinary GNU General Public License. This license, the GNU Lesser -General Public License, applies to certain designated libraries, and -is quite different from the ordinary General Public License. We use -this license for certain libraries in order to permit linking those -libraries into non-free programs. - - When a program is linked with a library, whether statically or using -a shared library, the combination of the two is legally speaking a -combined work, a derivative of the original library. The ordinary -General Public License therefore permits such linking only if the -entire combination fits its criteria of freedom. The Lesser General -Public License permits more lax criteria for linking other code with -the library. - - We call this license the "Lesser" General Public License because it -does Less to protect the user's freedom than the ordinary General -Public License. It also provides other free software developers Less -of an advantage over competing non-free programs. These disadvantages -are the reason we use the ordinary General Public License for many -libraries. However, the Lesser license provides advantages in certain -special circumstances. - - For example, on rare occasions, there may be a special need to -encourage the widest possible use of a certain library, so that it becomes -a de-facto standard. To achieve this, non-free programs must be -allowed to use the library. A more frequent case is that a free -library does the same job as widely used non-free libraries. In this -case, there is little to gain by limiting the free library to free -software only, so we use the Lesser General Public License. - - In other cases, permission to use a particular library in non-free -programs enables a greater number of people to use a large body of -free software. For example, permission to use the GNU C Library in -non-free programs enables many more people to use the whole GNU -operating system, as well as its variant, the GNU/Linux operating -system. - - Although the Lesser General Public License is Less protective of the -users' freedom, it does ensure that the user of a program that is -linked with the Library has the freedom and the wherewithal to run -that program using a modified version of the Library. - - The precise terms and conditions for copying, distribution and -modification follow. Pay close attention to the difference between a -"work based on the library" and a "work that uses the library". The -former contains code derived from the library, whereas the latter must -be combined with the library in order to run. - - GNU LESSER GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License Agreement applies to any software library or other -program which contains a notice placed by the copyright holder or -other authorized party saying it may be distributed under the terms of -this Lesser General Public License (also called "this License"). -Each licensee is addressed as "you". - - A "library" means a collection of software functions and/or data -prepared so as to be conveniently linked with application programs -(which use some of those functions and data) to form executables. - - The "Library", below, refers to any such software library or work -which has been distributed under these terms. A "work based on the -Library" means either the Library or any derivative work under -copyright law: that is to say, a work containing the Library or a -portion of it, either verbatim or with modifications and/or translated -straightforwardly into another language. (Hereinafter, translation is -included without limitation in the term "modification".) - - "Source code" for a work means the preferred form of the work for -making modifications to it. For a library, complete source code means -all the source code for all modules it contains, plus any associated -interface definition files, plus the scripts used to control compilation -and installation of the library. - - Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running a program using the Library is not restricted, and output from -such a program is covered only if its contents constitute a work based -on the Library (independent of the use of the Library in a tool for -writing it). Whether that is true depends on what the Library does -and what the program that uses the Library does. - - 1. You may copy and distribute verbatim copies of the Library's -complete source code as you receive it, in any medium, provided that -you conspicuously and appropriately publish on each copy an -appropriate copyright notice and disclaimer of warranty; keep intact -all the notices that refer to this License and to the absence of any -warranty; and distribute a copy of this License along with the -Library. - - You may charge a fee for the physical act of transferring a copy, -and you may at your option offer warranty protection in exchange for a -fee. - - 2. You may modify your copy or copies of the Library or any portion -of it, thus forming a work based on the Library, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) The modified work must itself be a software library. - - b) You must cause the files modified to carry prominent notices - stating that you changed the files and the date of any change. - - c) You must cause the whole of the work to be licensed at no - charge to all third parties under the terms of this License. - - d) If a facility in the modified Library refers to a function or a - table of data to be supplied by an application program that uses - the facility, other than as an argument passed when the facility - is invoked, then you must make a good faith effort to ensure that, - in the event an application does not supply such function or - table, the facility still operates, and performs whatever part of - its purpose remains meaningful. - - (For example, a function in a library to compute square roots has - a purpose that is entirely well-defined independent of the - application. Therefore, Subsection 2d requires that any - application-supplied function or table used by this function must - be optional: if the application does not supply it, the square - root function must still compute square roots.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Library, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Library, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote -it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Library. - -In addition, mere aggregation of another work not based on the Library -with the Library (or with a work based on the Library) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may opt to apply the terms of the ordinary GNU General Public -License instead of this License to a given copy of the Library. To do -this, you must alter all the notices that refer to this License, so -that they refer to the ordinary GNU General Public License, version 2, -instead of to this License. (If a newer version than version 2 of the -ordinary GNU General Public License has appeared, then you can specify -that version instead if you wish.) Do not make any other change in -these notices. - - Once this change is made in a given copy, it is irreversible for -that copy, so the ordinary GNU General Public License applies to all -subsequent copies and derivative works made from that copy. - - This option is useful when you wish to copy part of the code of -the Library into a program that is not a library. - - 4. You may copy and distribute the Library (or a portion or -derivative of it, under Section 2) in object code or executable form -under the terms of Sections 1 and 2 above provided that you accompany -it with the complete corresponding machine-readable source code, which -must be distributed under the terms of Sections 1 and 2 above on a -medium customarily used for software interchange. - - If distribution of object code is made by offering access to copy -from a designated place, then offering equivalent access to copy the -source code from the same place satisfies the requirement to -distribute the source code, even though third parties are not -compelled to copy the source along with the object code. - - 5. A program that contains no derivative of any portion of the -Library, but is designed to work with the Library by being compiled or -linked with it, is called a "work that uses the Library". Such a -work, in isolation, is not a derivative work of the Library, and -therefore falls outside the scope of this License. - - However, linking a "work that uses the Library" with the Library -creates an executable that is a derivative of the Library (because it -contains portions of the Library), rather than a "work that uses the -library". The executable is therefore covered by this License. -Section 6 states terms for distribution of such executables. - - When a "work that uses the Library" uses material from a header file -that is part of the Library, the object code for the work may be a -derivative work of the Library even though the source code is not. -Whether this is true is especially significant if the work can be -linked without the Library, or if the work is itself a library. The -threshold for this to be true is not precisely defined by law. - - If such an object file uses only numerical parameters, data -structure layouts and accessors, and small macros and small inline -functions (ten lines or less in length), then the use of the object -file is unrestricted, regardless of whether it is legally a derivative -work. (Executables containing this object code plus portions of the -Library will still fall under Section 6.) - - Otherwise, if the work is a derivative of the Library, you may -distribute the object code for the work under the terms of Section 6. -Any executables containing that work also fall under Section 6, -whether or not they are linked directly with the Library itself. - - 6. As an exception to the Sections above, you may also combine or -link a "work that uses the Library" with the Library to produce a -work containing portions of the Library, and distribute that work -under terms of your choice, provided that the terms permit -modification of the work for the customer's own use and reverse -engineering for debugging such modifications. - - You must give prominent notice with each copy of the work that the -Library is used in it and that the Library and its use are covered by -this License. You must supply a copy of this License. If the work -during execution displays copyright notices, you must include the -copyright notice for the Library among them, as well as a reference -directing the user to the copy of this License. Also, you must do one -of these things: - - a) Accompany the work with the complete corresponding - machine-readable source code for the Library including whatever - changes were used in the work (which must be distributed under - Sections 1 and 2 above); and, if the work is an executable linked - with the Library, with the complete machine-readable "work that - uses the Library", as object code and/or source code, so that the - user can modify the Library and then relink to produce a modified - executable containing the modified Library. (It is understood - that the user who changes the contents of definitions files in the - Library will not necessarily be able to recompile the application - to use the modified definitions.) - - b) Use a suitable shared library mechanism for linking with the - Library. A suitable mechanism is one that (1) uses at run time a - copy of the library already present on the user's computer system, - rather than copying library functions into the executable, and (2) - will operate properly with a modified version of the library, if - the user installs one, as long as the modified version is - interface-compatible with the version that the work was made with. - - c) Accompany the work with a written offer, valid for at - least three years, to give the same user the materials - specified in Subsection 6a, above, for a charge no more - than the cost of performing this distribution. - - d) If distribution of the work is made by offering access to copy - from a designated place, offer equivalent access to copy the above - specified materials from the same place. - - e) Verify that the user has already received a copy of these - materials or that you have already sent this user a copy. - - For an executable, the required form of the "work that uses the -Library" must include any data and utility programs needed for -reproducing the executable from it. However, as a special exception, -the materials to be distributed need not include anything that is -normally distributed (in either source or binary form) with the major -components (compiler, kernel, and so on) of the operating system on -which the executable runs, unless that component itself accompanies -the executable. - - It may happen that this requirement contradicts the license -restrictions of other proprietary libraries that do not normally -accompany the operating system. Such a contradiction means you cannot -use both them and the Library together in an executable that you -distribute. - - 7. You may place library facilities that are a work based on the -Library side-by-side in a single library together with other library -facilities not covered by this License, and distribute such a combined -library, provided that the separate distribution of the work based on -the Library and of the other library facilities is otherwise -permitted, and provided that you do these two things: - - a) Accompany the combined library with a copy of the same work - based on the Library, uncombined with any other library - facilities. This must be distributed under the terms of the - Sections above. - - b) Give prominent notice with the combined library of the fact - that part of it is a work based on the Library, and explaining - where to find the accompanying uncombined form of the same work. - - 8. You may not copy, modify, sublicense, link with, or distribute -the Library except as expressly provided under this License. Any -attempt otherwise to copy, modify, sublicense, link with, or -distribute the Library is void, and will automatically terminate your -rights under this License. However, parties who have received copies, -or rights, from you under this License will not have their licenses -terminated so long as such parties remain in full compliance. - - 9. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Library or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Library (or any work based on the -Library), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Library or works based on it. - - 10. Each time you redistribute the Library (or any work based on the -Library), the recipient automatically receives a license from the -original licensor to copy, distribute, link with or modify the Library -subject to these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties with -this License. - - 11. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Library at all. For example, if a patent -license would not permit royalty-free redistribution of the Library by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Library. - -If any portion of this section is held invalid or unenforceable under any -particular circumstance, the balance of the section is intended to apply, -and the section as a whole is intended to apply in other circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 12. If the distribution and/or use of the Library is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Library under this License may add -an explicit geographical distribution limitation excluding those countries, -so that distribution is permitted only in or among countries not thus -excluded. In such case, this License incorporates the limitation as if -written in the body of this License. - - 13. The Free Software Foundation may publish revised and/or new -versions of the Lesser General Public License from time to time. -Such new versions will be similar in spirit to the present version, -but may differ in detail to address new problems or concerns. - -Each version is given a distinguishing version number. If the Library -specifies a version number of this License which applies to it and -"any later version", you have the option of following the terms and -conditions either of that version or of any later version published by -the Free Software Foundation. If the Library does not specify a -license version number, you may choose any version ever published by -the Free Software Foundation. - - 14. If you wish to incorporate parts of the Library into other free -programs whose distribution conditions are incompatible with these, -write to the author to ask for permission. For software which is -copyrighted by the Free Software Foundation, write to the Free -Software Foundation; we sometimes make exceptions for this. Our -decision will be guided by the two goals of preserving the free status -of all derivatives of our free software and of promoting the sharing -and reuse of software generally. - - NO WARRANTY - - 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO -WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. -EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR -OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY -KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE -LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME -THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN -WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY -AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU -FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR -CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE -LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING -RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A -FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF -SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH -DAMAGES. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Libraries - - If you develop a new library, and you want it to be of the greatest -possible use to the public, we recommend making it free software that -everyone can redistribute and change. You can do so by permitting -redistribution under these terms (or, alternatively, under the terms of the -ordinary General Public License). - - To apply these terms, attach the following notices to the library. It is -safest to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least the -"copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - -Also add information on how to contact you by electronic and paper mail. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the library, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the - library `Frob' (a library for tweaking knobs) written by James Random Hacker. - - , 1 April 1990 - Ty Coon, President of Vice - -That's all there is to it! diff --git a/LICENSE.header b/LICENSE.header deleted file mode 100644 index 12170c2..0000000 --- a/LICENSE.header +++ /dev/null @@ -1,24 +0,0 @@ -/* -***** BEGIN LICENSE BLOCK ***** - -This project is eixx (Erlang C++ Interface) library. - -Copyright (C) 2010 Serge Aleynikov - -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -***** END LICENSE BLOCK ***** -*/ - diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..ab9759b --- /dev/null +++ b/Makefile @@ -0,0 +1,55 @@ +# vim:ts=4:sw=4:et +#------------------------------------------------------------------------------- +# Makefile helper for cmake +#------------------------------------------------------------------------------- +# Copyright (c) 2015 Serge Aleynikov +# Date: 2014-08-12 +#------------------------------------------------------------------------------- + +build ?= debug +BUILD_ARG := $(shell echo $(build) | tr 'A-Z' 'a-z') +REBOOSTR_FILE := .build/.bootstrap + +-include build/cache.mk + +VERBOSE := $(if $(findstring $(verbose),true 1),$(if $(findstring $(generator),ninja),-v,VERBOSE=1)) + +.DEFAULT_GOAL := all + +distclean: + @[ -n "$(DIR)" -a -d "$(DIR)" ] && echo "Removing $(DIR)" && rm -fr $(DIR) build inst || true + +bootstrap: + @$(MAKE) -f build-aux/bootstrap.mk --no-print-directory $@ $(MAKEOVERRIDES) + +rebootstrap: .build/.bootstrap + $(if $(build),$(filter-out build=%,$(shell cat $(REBOOSTR_FILE))) \ + build=$(BUILD_ARG),$(shell cat $(REBOOSTR_FILE))) --no-print-directory $(MAKEOVERRIDES) + +test: + CTEST_OUTPUT_ON_FAILURE=TRUE $(generator) -C$(DIR) $(VERBOSE) -j$(shell nproc) $@ + +info ver: + @$(MAKE) -sf build-aux/bootstrap.mk --no-print-directory $@ + +vars: + @cmake -H. -B$(DIR) -LA + +tree: + @tree build -I "*.cmake|CMake*" --matchdirs -F -a $(if $(full),-f) + +build/cache.mk: + +.build/.bootstrap: + @echo "Rerun 'make bootstrap'!" && false + +.DEFAULT: + @if [ ! -f build/cache.mk ]; then \ + $(MAKE) -f build-aux/bootstrap.mk --no-print-directory; \ + else \ + $(generator) -C$(DIR) $(VERBOSE)\ + $(if $(findstring $(generator),ninja),, --no-print-directory)\ + -j$(shell nproc) $@;\ + fi + +.PHONY: bootstrap rebootstrap distclean info test doc diff --git a/Makefile.am b/Makefile.am deleted file mode 100644 index 85f44c4..0000000 --- a/Makefile.am +++ /dev/null @@ -1,34 +0,0 @@ -SUBDIRS = src test -ACLOCAL_AMFLAGS = -I build-aux - -pkgconfigdir = $(libdir)/pkgconfig -nodist_pkgconfig_DATA = src/eixx.pc - -clean-local: - -rm -fr @default_prefix@ - -docs: build-aux/Doxyfile - $(MKDIR_P) @docdir@ - doxygen build-aux/Doxyfile - -build-aux/Doxyfile: build-aux/Doxyfile.in - $(SED) -e 's|[@]docdir@|${docdir}|g' \ - -e 's|[@]PACKAGE@|${PACKAGE}|g' \ - -e 's|[@]PACKAGE_VERSION@|${PACKAGE_VERSION}|g' $< > "$@" - -CLEANFILES = stamp-h1 -DISTCLEANFILES = config.h.in~ config.log build-aux/Doxyfile -MAINTAINERCLEANFILES = configure build-aux/l*.m4 build-aux/*.sh \ - build-aux/config.* build-aux/missing build-aux/depcomp \ - build-aux/install-sh build-aux/Doxyfile \ - aclocal.m4 Makefile.in src/Makefile.in test/Makefile.in \ - src/eixx.pc - -EXTRA_DIST = README LICENSE LICENSE.header license.sh \ - build-aux/boost.m4 build-aux/Doxyfile.in \ - bootstrap - -installdir = $(prefix) -install_DATA = README LICENSE - -.PHONY: docs doc diff --git a/NOTICES b/NOTICES new file mode 100644 index 0000000..60a62e1 --- /dev/null +++ b/NOTICES @@ -0,0 +1,5 @@ +Erlang Interface C++ (eixx) +Copyright (c) 2010-2016 Serge Aleynikov + +The eixx project is developed by Serge Aleynikov . +It's source code is hosted at: https://github.com/saleyn/eixx. diff --git a/README b/README deleted file mode 100644 index 7362b5d..0000000 --- a/README +++ /dev/null @@ -1,182 +0,0 @@ -eixx - Erlang C++ Interface Library -=================================== - -This library provides a set of classes for convenient marshaling -of Erlang terms between processes as well as connecting to other -distributed Erlang nodes from a C++ application. - -The marshaling classes are built on top of ei library included in -{@link http://www.erlang.org/doc/apps/erl_interface erl_interface}. -It is largely inspired by the {@link http://code.google.com/p/epi epi} -project, but is a complete rewrite with many new features and -optimization enhancements. - -This library adds on the following features: - - Use of reference-counted smart pointers for complex terms and - by-value copied simple terms (e.i. integers, doubles, bool, atoms). - - Ability to provide custom memory allocators. - - Encoding/decoding of nested terms using a single function call - (eterm::encode() and eterm::eterm() constructor). - - Global atom table for fast manipulation of atoms. - -The library consists of two separate parts: - - Term marshaling (included by marshal.hpp or eixx.hpp) - - Distributed node connectivity (included by connect.hpp or eixx.hpp) - -The connectivity library implements a richer set of features than -what's available in erl_interface - it fully supports process -linking and monitoring. The library is fully asynchronous and allows -handling many connections and mailboxes in one OS thread. - -Ths library is dependend on {@link http://www.boost.org BOOST} -project and erl_interface, which is a part of the -{@link www.erlang.org Erlang} distribution. - -Downloading -=========== - Repository location: http://github.com/saleyn/eixx - - $ git clone git@github.com:saleyn/eixx.git - -Building -======== - Make sure that you have autoconf-archive package installed: - http://www.gnu.org/software/autoconf-archive - - Run: - $ ./bootstrap - $ ./configure --with-boost="/path/to/boost" [--with-erlang="/path/to/erlang"] \ - [--prefix="/target/install/path"] - $ make - $ make install # Default install path is ./install - -Author -====== - Serge Aleynikov - -LICENSE -======= - GNU Lesser General Public License - -Example -======= - -Here's an example use of the eixx library: - - void on_message(otp_mailbox& a_mbox, boost::system::error_code& ec) { - // On timeout ec == boost::asio::error::timeout - if (ec == boost::asio::error::timeout) { - std::cout << "Mailbox " << a_mbox.self() << " timeout: " - << ec.message() << std::endl; - } else { - // The mailbox has a queue of transport messages. - // Dequeue next message from the mailbox. - boost::scoped_ptr dist_msg; - - while (dist_msg.reset(a_mbox.receive())) { - std::cout << "Main mailbox got a distributed transport message:\n " - << *p << std::endl; - - // Use the following pattern for a pattern match on the message. - static const eterm s_pattern = eterm::format("{From, {command, Cmd}}"); - - varbind binding; - - if (s_pattern.match(dist_msg->msg(), &binding)) { - const eterm* cmd = binding->find("Cmd"); - std::cout << "Got a command " << *cmd << std::endl; - // Process command, e.g.: - // process_command(binding["From"]->to_pid(), *cmd); - } - } - } - - // Schedule next async receive of a message (can also provide a timeout). - a_mbox.async_receive(&on_message); - } - - void on_connect(otp_connection* a_con, const std::string& a_error) { - if (!a_error.empty()) { - std::cout << a_error << std::endl; - return; - } - - // Illustrate creation of Erlang terms. - eterm t1 = eterm::format("{ok, ~i}", 10); - eterm t2 = tuple::make(10, 1.0, atom("test"), "abc"); - eterm t3("This is a string"); - eterm t4(tuple::make(t1, t2, t3)); - - otp_node* node = a_con->node(); - otp_mailbox* mbox = node->get_mailbox("main"); - - // Send a message: {{ok, 10}, {10, 1.0, 'test', "abc"}, "This is a string"} - // to an Erlang process named 'test' running - // on node "abc@myhost" - - node->send(mbox.self(), a_con->remote_node(), atom("test"), t4); - } - - int main() { - use namespace eixx; - - otp_node node("abc"); - - otp_mailbox* mbox = node.create_mailbox("main"); - - node.connect(&on_connect, "abc@myhost"); - - // Asynchronously receive a message with a deadline of 10s: - mbox->async_receive(&on_message, 10000); - - node.run(); - } - - - -Testing distributed transport -============================= - -$ make - -Run tests: - - $ LD_LIBRARY_PATH=$LD_LIBRARY_PATH:. ./test_eterm - -Test distributed transport: - - $ cd src - - # In this example we assume the host name of "fc12". - - [Shell A]$ erl -sname abc - (abc@fc12)1> register(test, self()). - - [Shell B]$ ./test_node -n a@fc12 -r abc@fc12 - Connected to node: abc@fc12 - I/O server got a message: - #DistMsg{ - type=SEND, - cntrl={2,'',#Pid}, - msg={io_request,#Pid,#Pid, - {put_chars,<<"This is a test string">>}}} - -The message above is a result of the on_connect() handler in test_node.cpp -issuing an rpc call to the abc@fc12 node of `io:put_chars("This is a test -string")'. This the call selects a locally registered process called -'io_server' as the group leader for this rpc call, the I/O output is sent -to that mailbox. - -Now you can try to send a message from Erlang to the 'main' mailbox -registered on the C++ node: - - [Shell A] - (abc@fc12)2> {main, a@fc12} ! "This is a test!". - - [Shell B] - Main mailbox got a message: - #DistMsg{ - type=REG_SEND, - cntrl={6,#Pid,'',main}, - msg="This is a test!"} - diff --git a/README.md b/README.md new file mode 100644 index 0000000..895219b --- /dev/null +++ b/README.md @@ -0,0 +1,293 @@ +## eixx - Erlang C++ Interface Library ## + +This library provides a set of classes for convenient marshaling +of Erlang terms between processes as well as connecting to other +distributed Erlang nodes from a C++ application. + +The marshaling classes are built on top of ei library included in +http://www.erlang.org/doc/apps/erl_interface. + +The library consists of two separate parts: + - Term marshaling (included by eterm.hpp or eixx.hpp). + - Distributed node connectivity (included by connect.hpp or eixx.hpp) + +The term marshaling part of the library has following features: + - Encoding/decoding of nested terms using a single function call + (eterm::encode() and eterm::eterm() constructor). + - Global atom table for fast manipulation of atoms. + - Ability to provide custom memory allocators. + - Use of reference-counted smart pointers for complex terms and + by-value copied simple terms (e.i. integers, doubles, bool, atoms). + +If you are simply doing term marshalling only the eterm.hpp header along +with one of the alloc headers is needed. This allows for header-only +usage of the marshalling capabilities. + +The connectivity library implements a richer set of features than +what's available in erl_interface - it fully supports process +linking and monitoring. The library is fully asynchronous and allows +handling many connections and mailboxes in one OS thread. + +Ths library is dependend on http://www.boost.org project and +erl_interface, which is a part of the www.erlang.org distribution. + +### Downloading ### + +Repository location: http://github.com/saleyn/eixx + + $ git clone git@github.com:saleyn/eixx.git + +### Building ### + +This library is dependent on BOOST. + +If you need to customize location of BOOST or installation prefix, create a file called +`.cmake-args.${HOSTNAME}`. Alternatively if you are doing multi-host build with +identical configuration, create a file call `.cmake-args`. E.g.: + +There are three sets of variables present in this file: + +1. Build and install locations. + + * `DIR:BUILD=...` - Build directory + * `DIR:INSTALL=...` - Install directory + + They may contain macros: + + * `@PROJECT@` - name of current project (from CMakeList.txt) + * `@VERSION@` - project version number (from CMakeList.txt) + * `@BUILD@` - build type (from command line) + * `${...}` - environment variable + +2. `ENV:VAR=...` - Environment var set before running cmake + +3. `VAR=...` - Variable passed to cmake with -D prefix + +Example: +``` +$ cat > .cmake-args.${HOSTNAME} +DIR:BUILD=/tmp/@PROJECT@/build +DIR:INSTALL=/opt/pkt/@PROJECT@/@VERSION@ +ENV:BOOST_ROOT=/opt/pkg/boost/current +ENV:BOOST_LIBRARYDIR=/opt/pkg/boost/current/gcc/lib +PKG_ROOT_DIR=/opt/pkg +``` +* Run: +``` +$ make bootstrap [toolchain=gcc|clang] [build=Debug|Release] \ + [generator=make|ninja] [prefix=/usr/local] [verbose=true] +$ make [verbose=true] +$ make install # Default install path is /usr/local +``` +There is a slight difference between installing release and debug builds. The release +build installer also tries to install a "debug version" of eixx (`libeixx_d.so`) that +must be build using build type `"debug"`. So for a release build do: +``` +$ make bootstrap build=debug +$ make src/libeixx_d.so +$ make rebootstrap build=release +$ make +$ make install +``` +After running `make bootstrap` two local links are created `build` and `inst` +pointing to build and installation directories. + +* To do a full cleanup of the current build and rerun bootstrap with +previously chosen options, do: +``` +$ make distclean +$ make rebootstrap [toolchain=gcc|clang] [build=Debug|Release] +``` +Note that the `rebootstrap` command remembers previous bootstrap options, but +if you give it arguments they will override the old ones. + +### Author ### + +Serge Aleynikov + +### LICENSE ### + +Apache Public License v2.0 + +### Example ### + +Manipulating Erlang terms is quite simple: +```cpp +eterm i = 10; +eterm s = "abc"; +eterm a = atom("ok"); +eterm t = {20.0, i, s, a}; // Constructs a tuple +eterm e = list{}; // Constructs an empty list +eterm l = list{i, 100.0, s, {a, 30}, list{}}; // Constructs a list +``` +A convenient eterm::format() function implements an expression parser: + +```cpp +eterm t1 = eterm::format("{ok, 10}"); +eterm t2 = eterm::format("[1, 2, ok, [{one, 10}, {two, 20}]]"); +eterm t3 = eterm::format("A::int()"); // t3 is a variable that can be matched +``` + +Pattern matching is done by constructing a pattern, and matching a term +against it. If varbind is provided, it'll store the values of all matched variables: + +```cpp +static const eterm s_pattern = eterm::format("{ok, A::string()}"); + +eterm value = {atom("ok"), "abc"}; + +varbind binding; + +if (value.match(s_pattern, &binding)) + std::cout << "Value of variable A: " << binding["A"].to_string() << std::endl; +``` +Erlang terms manipulation is pretty efficient. Creation/copying times of polymorphic +eterm's are shown below (project compiled in the `release` mode): +``` +$ build/test/test-perf + 1000000 iterations + Integer | latency: 6ns, speed: 150015001/s + Double | latency: 3ns, speed: 300030003/s + Bool | latency: 3ns, speed: 300030003/s + String | latency: 56ns, speed: 17646955/s + Atom1 | latency: 33ns, speed: 30000300/s + Atom2 | latency: 36ns, speed: 27272479/s + Binary1 | latency: 46ns, speed: 21428877/s + Binary2 | latency: 6ns, speed: 150015001/s + Tuple1 | latency: 73ns, speed: 13636239/s + Tuple2 | latency: 20ns, speed: 50000000/s + List1 | latency: 73ns, speed: 13636239/s + List2 | latency: 23ns, speed: 42857755/s + Apply speed | latency: 1190ns, speed: 840336/s + Apply/Create speed | latency: 966ns, speed: 1034482/s + Nested lists/tuples (1) speed | latency: 566ns, speed: 1764695/s + Nested lists/tuples (2) speed | latency: 466ns, speed: 2142887/s + Simple pattern match (1) | latency: 33ns, speed: 30003000/s + Nested pattern match (2) | latency: 333ns, speed: 2999940/s +``` +The last four tests illustrate various ways of creating nested terms with tuples, +lists and other types. The other tests illustrate creation times of primitive +eterm types. + +Aside from providing functionality that allows to manipulate Erlang terms, this +library implements Erlang distributed transport that allows a C++ program to connect +to an Erlang node, exchange messages, make RPC calls, and receive I/O requests. +Here's an example use of the eixx library: + +```cpp +void on_message(otp_mailbox& a_mbox, boost::system::error_code& ec) { + // On timeout ec == boost::asio::error::timeout + if (ec == boost::asio::error::timeout) { + std::cout << "Mailbox " << a_mbox.self() << " timeout: " + << ec.message() << std::endl; + } else { + // The mailbox has a queue of transport messages. + // Dequeue next message from the mailbox. + std::unique_ptr dist_msg; + + while (dist_msg.reset(a_mbox.receive())) { + std::cout << "Main mailbox got a distributed transport message:\n " + << *dist_msg << std::endl; + + // Compile the following pattern into the corresponding abstract tree. + // The expression must be a valid Erlang term + static const eterm s_pattern = eterm::format("{From, {command, Cmd}}"); + + varbind binding; + + // Pattern match the message and bind From and Cmd variables + if (dist_msg->msg().match(s_pattern, &binding)) { + const eterm* cmd = binding->find("Cmd"); + std::cout << "Got a command " << *cmd << std::endl; + // Process command, e.g.: + // process_command(binding["From"]->to_pid(), *cmd); + } + } + } + + // Schedule next async receive of a message (can also provide a timeout). + a_mbox.async_receive(&on_message); +} + +void on_connect(otp_connection* a_con, const std::string& a_error) { + if (!a_error.empty()) { + std::cout << a_error << std::endl; + return; + } + + // Illustrate creation of Erlang terms. + eterm t1 = eterm::format("{ok, ~i}", 10); + eterm t2 = tuple::make(10, 1.0, atom("test"), "abc"); + eterm t3("This is a string"); + eterm t4(tuple::make(t1, t2, t3)); + + otp_node* node = a_con->node(); + otp_mailbox* mbox = node->get_mailbox("main"); + + // Send a message: {{ok, 10}, {10, 1.0, 'test', "abc"}, "This is a string"} + // to an Erlang process named 'test' running + // on node "abc@myhost" + + node->send(mbox.self(), a_con->remote_node(), atom("test"), t4); +} + +int main() { + use namespace eixx; + + otp_node node("abc"); + + otp_mailbox* mbox = node.create_mailbox("main"); + + node.connect(&on_connect, "abc@myhost"); + + // Asynchronously receive a message with a deadline of 10s: + mbox->async_receive(&on_message, 10000); + + node.run(); +} +``` + +### Testing distributed transport ### + + $ make + +Run tests: + + $ LD_LIBRARY_PATH=$LD_LIBRARY_PATH:. ./test_eterm + +Test distributed transport: + + $ cd src + + # In this example we assume the host name of "fc12". + + [Shell A]$ erl -sname abc + (abc@fc12)1> register(test, self()). + + [Shell B]$ ./test_node -n a@fc12 -r abc@fc12 + Connected to node: abc@fc12 + I/O server got a message: + #DistMsg{ + type=SEND, + cntrl={2,'',#Pid}, + msg={io_request,#Pid,#Pid, + {put_chars,<<"This is a test string">>}}} + +The message above is a result of the `on_connect()` handler in `test_node.cpp` +issuing an rpc call to the abc@fc12 node of `io:put_chars("This is a test +string")'. This the call selects a locally registered process called +'io_server' as the group leader for this rpc call, the I/O output is sent +to that mailbox. + +Now you can try to send a message from Erlang to the 'main' mailbox +registered on the C++ node: + + [Shell A] + (abc@fc12)2> {main, a@fc12} ! "This is a test!". + + [Shell B] + Main mailbox got a message: + #DistMsg{ + type=REG_SEND, + cntrl={6,#Pid,'',main}, + msg="This is a test!"} diff --git a/TODO b/TODO index 2636748..ea48e18 100644 --- a/TODO +++ b/TODO @@ -1,2 +1,34 @@ 1. Check serialization of long: -6478876429381754229 2. Check encoding binary encoding of: ["ecg",-6478876429381754229,Pid,"example_core",29633] +3. Change internal representatino of eterm, so that the refcount is moved from blob_t to + eterm itself, so the layout of eterm would be the following: + a. change eterm_type's storage: + enum eterm_type : char; + b. change eterm: + struct eterm { + eterm_type m_type; + atomic m_rc; + union vartype {} m_data; + } +4. Protocol change in R19.2. See this report on mailing list (2010-10-12): + Got into interesting 'does not work' situation while connecting newly + deployed c-node (compiled with otp 19.0 libraries) with older 18.3 + erlang node: nodes were unable to communicate with messages + + 2016-10-12 18:43:28.404 [warning] emulator '' got a + corrupted external term from '' on distribution channel ... + + logged on erlang side. + + After some research, root cause was isolated: C-node was initialized using + ei_connect_xinit with random() % 16 for its creation id. In 18.x, Pid of + C-node was encoded as ERL_PID_EXT and used only two lower bits of creation id. + However, in 19.x there is a new Pid wire presentation, ERL_NEW_PID_EXT, which + encodes all 32 bits of creation id. Worse yet, this presentation is + automatically selected for encoding any Pid with creation id > 3, and, + as this presentation is not known by older 18.x nodes, this leads to + connection drop. + + Solution: if you expect your C-nodes to communicate with pre-19.x erlang + nodes, you may use only values 0..3 for creation id. In my case I just + replaced random() % 16 with random() % 4. diff --git a/bootstrap b/bootstrap deleted file mode 100755 index 843a281..0000000 --- a/bootstrap +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh - -autoreconf -v --install diff --git a/build-aux/AlignOf.cmake b/build-aux/AlignOf.cmake new file mode 100644 index 0000000..4f0e60f --- /dev/null +++ b/build-aux/AlignOf.cmake @@ -0,0 +1,45 @@ +MACRO(ALIGNOF TYPE LANG NAME) + + IF(NOT ALIGNOF_${NAME}) + + # + # Try to compile and run a foo grogram. + # The alignment result will be stored in ALIGNOF_${CHECK_TYPE} + # + + SET(INCLUDE_HEADERS + "#include + #include + #include ") + + FOREACH(File ${CMAKE_REQUIRED_INCLUDES}) + SET(INCLUDE_HEADERS "${INCLUDE_HEADERS}\n#include <${File}>\n") + ENDFOREACH() + #IF(HAVE_STDINT_H) + SET(INCLUDE_HEADERS "${INCLUDE_HEADERS}\n#include \n") + #ENDIF() + + FILE (WRITE "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeTmp/c_get_${NAME}_alignment.${LANG}" + "${INCLUDE_HEADERS} + int main(){ + char diff; + struct foo {char a; ${TYPE} b;}; + struct foo *p = (struct foo *) malloc(sizeof(struct foo)); + diff = ((char *)&p->b) - ((char *)&p->a); + return diff; + }" + ) + + TRY_RUN(ALIGNOF_${NAME} COMPILE_RESULT "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeTmp/" + "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeTmp/c_get_${NAME}_alignment.${LANG}" + COMPILE_OUTPUT_VARIABLE "ALIGNOF_${NAME}_COMPILE_VAR") + + IF (NOT COMPILE_RESULT) + MESSAGE(FATAL_ERROR "Check alignment of ${TYPE} in ${LANG}: compilation failed: ${ALIGNOF_${NAME}_COMPILE_VAR}") + ELSE() + MESSAGE(STATUS "Check alignment of ${TYPE} in ${LANG}: ${ALIGNOF_${NAME}}") + ENDIF() + + ENDIF() + +ENDMACRO() diff --git a/build-aux/CMakeEx.txt b/build-aux/CMakeEx.txt new file mode 100644 index 0000000..00f69aa --- /dev/null +++ b/build-aux/CMakeEx.txt @@ -0,0 +1,69 @@ +#------------------------------------------------------------------------------- +# Colorization support +#------------------------------------------------------------------------------- +if(NOT WIN32) + string(ASCII 27 Esc) + string(CONCAT Esc ${Esc} "[") # So that vim highlighting doesn't get screwed + set(Red "${Esc}0;31m") + set(Green "${Esc}0;32m") + set(Yellow "${Esc}0;33m") + set(Blue "${Esc}0;34m") + set(Magenta "${Esc}0;35m") + set(Cyan "${Esc}0;36m") + set(White "${Esc}0;37m") + set(BoldRed "${Esc}1;31m") + set(BoldGreen "${Esc}1;32m") + set(BoldYellow "${Esc}1;33m") + set(BoldBlue "${Esc}1;34m") + set(BoldMagenta "${Esc}1;35m") + set(BoldCyan "${Esc}1;36m") + set(BoldWhite "${Esc}1;37m") + set(ClrBold "${Esc}1m") + set(ClrReset "${Esc}m") +endif() + +#------------------------------------------------------------------------------- +# File downloading function +#------------------------------------------------------------------------------- +function(download filename url) + if (NOT EXISTS ${filename}) + file(DOWNLOAD ${url} ${filename} + STATUS status LOG log ) + list(GET status 0 status_code) + list(GET status 1 status_string) + + if(NOT status_code EQUAL 0) + file(APPEND ${CMAKE_BINARY_DIR}/CMakeFiles/CMakeOutput.log + "Failed to download ${url}\n" + " Reason: ${status_string}\n\n" + "${log}" + ) + message(FATAL_ERROR + "${Red}Failed to download ${url}${ClrReset}" + " Reason: ${status_string}" + ) + else() + message(STATUS "${ClrBold}Downloaded ${url}${ClrReset}") + message(STATUS "${ClrBold} -> ${filename}${ClrReset}") + endif() + endif() +endfunction(download) + +#------------------------------------------------------------------------------- +# prefix and suffix each element of list by ${prefix}elemnt${suffix} +#------------------------------------------------------------------------------- +macro(PREFIX list_name prefix) + # create empty list - necessary? + set(${list_name}_TMP) + + # prefix and suffix (optional 3rd parameter) elements + foreach(l ${${list_name}}) + list(APPEND ${list_name}_TMP ${prefix}${l}${ARGV3} ) + endforeach() + + # replace list by tmp list + set(${list_name} "${${list_name}_TMP}") + unset(${list_name}_TMP) +endmacro(PREFIX) + + diff --git a/build-aux/CMakeInit.txt b/build-aux/CMakeInit.txt new file mode 100644 index 0000000..500fd78 --- /dev/null +++ b/build-aux/CMakeInit.txt @@ -0,0 +1,12 @@ +SET (CMAKE_C_FLAGS_INIT "-Wall -std=c99") +SET (CMAKE_C_FLAGS_DEBUG_INIT "-g -O0") +SET (CMAKE_C_FLAGS_MINSIZEREL_INIT "-Os -DNDEBUG") +SET (CMAKE_C_FLAGS_RELEASE_INIT "-O3 -DNDEBUG") +SET (CMAKE_C_FLAGS_RELWITHDEBINFO_INIT "-O2 -g") + +SET (CMAKE_CXX_FLAGS_INIT "-Wall -march=native -Wno-deprecated -Wno-deprecated-declarations") +SET (CMAKE_CXX_STANDARD 17) +SET (CMAKE_CXX_FLAGS_DEBUG_INIT "-g -O0") +SET (CMAKE_CXX_FLAGS_MINSIZEREL_INIT "-Os -DNDEBUG") +SET (CMAKE_CXX_FLAGS_RELEASE_INIT "-O3 -DNDEBUG") +SET (CMAKE_CXX_FLAGS_RELWITHDEBINFO_INIT "-O2 -g") diff --git a/build-aux/Doxyfile.in b/build-aux/Doxyfile.in index f1510c9..38dc464 100644 --- a/build-aux/Doxyfile.in +++ b/build-aux/Doxyfile.in @@ -25,7 +25,7 @@ DOXYFILE_ENCODING = UTF-8 # The PROJECT_NAME tag is a single word (or a sequence of words surrounded # by quotes) that should identify the project. -PROJECT_NAME = DMF +PROJECT_NAME = @PROJECT_NAME@ # The PROJECT_NUMBER tag can be used to enter a project or revision number. # This could be handy for archiving the generated documentation or @@ -564,8 +564,11 @@ WARN_LOGFILE = # directories like "/usr/src/myproject". Separate the files or directories # with spaces. -INPUT = ./lib/perc/src \ - ./lib/perc/include/dmf +INPUT = @CMAKE_SOURCE_DIR@/src \ + @CMAKE_SOURCE_DIR@/include/@PROJECT_NAME@ \ + @CMAKE_SOURCE_DIR@/include/@PROJECT_NAME@/marshal \ + @CMAKE_SOURCE_DIR@/include/@PROJECT_NAME@/connect \ + @CMAKE_SOURCE_DIR@/include/@PROJECT_NAME@/util # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is @@ -594,7 +597,7 @@ RECURSIVE = NO # excluded from the INPUT source files. This way you can easily exclude a # subdirectory from a directory tree whose root is specified with the INPUT tag. -EXCLUDE = ./lib/perc/include/dmf/detail/mean_variance.hpp +EXCLUDE = # The EXCLUDE_SYMLINKS tag can be used select whether or not files or # directories that are symbolic links (a Unix filesystem feature) are excluded diff --git a/build-aux/FindErlang.cmake b/build-aux/FindErlang.cmake new file mode 100644 index 0000000..dd31321 --- /dev/null +++ b/build-aux/FindErlang.cmake @@ -0,0 +1,95 @@ +# vim:ts=2:sw=2:et +# - Find Erlang +# This module finds if Erlang is installed and determines where the +# include files and libraries are. This code sets the following +# variables: +# +# Erlang_ERL = the full path to the Erlang runtime +# Erlang_COMPILE = the full path to the Erlang compiler +# Erlang_EI_DIR = the full path to the Erlang erl_interface path +# Erlang_ERTS_DIR = the full path to the Erlang erts path +# Erlang_EI_INCLUDE_DIR = /include appended to Erlang_EI_DIR +# Erlang_EI_LIBRARY_DIR = /lib appended to Erlang_EI_DIR +# Erlang_ERTS_INCLUDE_DIR = /include appended to Erlang_ERTS_DIR +# Erlang_ERTS_LIBRARY_DIR = /lib appended to Erlang_ERTS_DIR +# + +IF (NOT Erlang_DIR) + SET(ERLANG_BIN_PATH + $ENV{ERLANG_HOME}/bin + /opt/bin + /sw/bin + /usr/bin + /usr/local/bin + /opt/local/bin + ) + FIND_PROGRAM(Erlang_ERL + NAMES erl + PATHS ${ERLANG_BIN_PATH} + ) + + FIND_PROGRAM(Erlang_COMPILE + NAMES erlc + PATHS ${ERLANG_BIN_PATH} + ) + + EXECUTE_PROCESS( + COMMAND erl -noshell -eval "io:format(\"~s\", [code:lib_dir()])" -s erlang halt + OUTPUT_VARIABLE Erlang_OTP_LIB_DIR + ) + + EXECUTE_PROCESS( + COMMAND erl -noshell -eval "io:format(\"~s\", [code:root_dir()])" -s erlang halt + OUTPUT_VARIABLE Erlang_OTP_ROOT_DIR + ) + + EXECUTE_PROCESS( + COMMAND + erl -noshell -eval "io:format(\"~s\", [code:lib_dir(erl_interface)])" -s erlang halt + OUTPUT_VARIABLE Erlang_EI_DIR + ) + + EXECUTE_PROCESS( + COMMAND + erl -noshell -eval "io:format(\"~s\", [erlang:system_info(otp_release)])" -s erlang halt + OUTPUT_VARIABLE Erlang_OTP_VERSION + ) + + MESSAGE(STATUS "Using Erlang OTP: ${Erlang_OTP_ROOT_DIR} - found OTP version ${Erlang_OTP_VERSION}") + + EXECUTE_PROCESS( + COMMAND ls ${Erlang_OTP_ROOT_DIR} + COMMAND grep erts + COMMAND sort -n + COMMAND tail -1 + COMMAND tr -d \n + OUTPUT_VARIABLE Erlang_ERTS_DIR + ) + + MESSAGE(STATUS "Using erl_interface version: ${Erlang_EI_DIR}") + MESSAGE(STATUS "Using erts version: ${Erlang_ERTS_DIR}") + + SET(Erlang_EI_DIR ${Erlang_EI_DIR} CACHE STRING "Erlang EI Dir") + SET(Erlang_EI_INCLUDE_DIR ${Erlang_OTP_ROOT_DIR}/usr/include CACHE STRING "Erlang EI Include Dir") + SET(Erlang_EI_LIBRARY_DIR ${Erlang_OTP_ROOT_DIR}/usr/lib CACHE STRING "Erlang EI Libary Dir") + SET(Erlang_EI_LIBRARIES ei CACHE STRING "Erlang EI Libraries") + + SET(Erlang_DIR ${Erlang_OTP_ROOT_DIR} CACHE STRING "Erlang Root Dir") + SET(Erlang_ERTS_DIR ${Erlang_OTP_ROOT_DIR}/${Erlang_ERTS_DIR}) + SET(Erlang_ERTS_INCLUDE_DIR ${Erlang_OTP_ROOT_DIR}/${Erlang_ERTS_DIR}/include) + SET(Erlang_ERTS_LIBRARY_DIR ${Erlang_OTP_ROOT_DIR}/${Erlang_ERTS_DIR}/lib) + + FIND_PROGRAM(Erlang_ARCHIVE + NAMES zip + PATHS ${ERLANG_BIN_PATH} + ) + MARK_AS_ADVANCED( + Erlang_ERL + Erlang_COMPILE + Erlang_ARCHIVE +# Erlang_EI_DIR +# Erlang_EI_INCLUDE_DIR +# Erlang_EI_LIBRARY_DIR + ) + +ENDIF(NOT Erlang_DIR) diff --git a/build-aux/LICENSE.header b/build-aux/LICENSE.header new file mode 100644 index 0000000..b1ac255 --- /dev/null +++ b/build-aux/LICENSE.header @@ -0,0 +1,22 @@ +/* +***** BEGIN LICENSE BLOCK ***** + +This project is eixx (Erlang C++ Interface) library. + +Copyright (C) 2010 Serge Aleynikov + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +***** END LICENSE BLOCK ***** +*/ + diff --git a/build-aux/bootstrap.mk b/build-aux/bootstrap.mk new file mode 100644 index 0000000..c43cae7 --- /dev/null +++ b/build-aux/bootstrap.mk @@ -0,0 +1,141 @@ +# vim:ts=4:sw=4:et +#------------------------------------------------------------------------------- +# Bootstrapping cmake +#------------------------------------------------------------------------------- +# Copyright (c) 2015 Serge Aleynikov +# Date: 2014-08-12 +#------------------------------------------------------------------------------- + +PROJECT := $(shell sed -n '/^project/{s/^project. *\([a-zA-Z0-9]\{1,\}\).*/\1/p; q;}'\ + CMakeLists.txt) +VERSION := $(shell sed -n '/^project/{s/^.\{1,\}VERSION \{1,\}//; s/[^\.0-9]\{1,\}//; p; q;}'\ + CMakeLists.txt) + +HOSTNAME := $(shell hostname) + +# Options file is either: .cmake-args.$(HOSTNAME) or .cmake-args +OPT_FILE := .cmake-args.$(HOSTNAME) +ifeq "$(wildcard $(OPT_FILE))" "" + OPT_FILE := .cmake-args + ifeq "$(wildcard $(OPT_FILE))" "" + OPT_FILE := "/dev/null" + endif +endif + +#------------------------------------------------------------------------------- +# Default target +#------------------------------------------------------------------------------- +all: + @echo + @echo "Run: make bootstrap [toolchain=gcc|clang|intel] [verbose=true] \\" + @echo " [generator=ninja|make] [build=Debug|Release]" + @echo + @echo "To customize cmake variables, create a file with VAR=VALUE pairs:" + @echo " '.cmake-args.$(HOSTNAME)' or '.cmake-args'" + @echo "" + @echo "There are three sets of variables present there:" + @echo " 1. DIR:BUILD=... - Build directory" + @echo " DIR:INSTALL=... - Install directory" + @echo + @echo " They may contain macros:" + @echo " @PROJECT@ - name of current project (from CMakeList.txt)" + @echo " @VERSION@ - project version number (from CMakeList.txt)" + @echo " @BUILD@ - build type (from command line)" + @echo " \$${...} - environment variable" + @echo + @echo " 2. ENV:VAR=... - Environment var set before running cmake" + @echo + @echo " 3. VAR=... - Variable passed to cmake with -D prefix" + @echo + @echo " Lines beginning with '#' are considered to be comments" + @echo + +toolchain ?= gcc +build ?= Debug +# Convert build to lower case: +BUILD := $(shell echo $(build) | tr 'A-Z' 'a-z') + +ifeq (,$(findstring $(BUILD),debug release relwithdefinfo minsizerel)) + $(error Invalid build type: $(build)) +endif + +# Function that replaces variables in a given entry in a file +# E.g.: $(call substitute,ENTRY,FILENAME) +substitute = $(shell sed -n '/^$(1)=/{s!$(1)=!!; s!/\{1,\}$$!!; s!@PROJECT@!$(PROJECT)!g; s!@VERSION@!$(VERSION)!g; s!@BUILD@!$(BUILD)!g; p; q;}' $(2) 2>/dev/null) +BLD_DIR := $(call substitute,DIR:BUILD,$(OPT_FILE)) +ROOT_DIR := $(dir $(abspath include)) +DEF_BLD_DIR := $(ROOT_DIR:%/=%)/build +DIR := $(if $(BLD_DIR),$(BLD_DIR),$(DEF_BLD_DIR)) +PREFIX := $(call substitute,DIR:INSTALL,$(OPT_FILE)) +prefix ?= $(if $(PREFIX),$(PREFIX),/usr/local) +generator ?= make + +#------------------------------------------------------------------------------- +# info target +#------------------------------------------------------------------------------- +info: + @echo "PROJECT: $(PROJECT)" + @echo "HOSTNAME: $(HOSTNAME)" + @echo "VERSION: $(VERSION)" + @echo "OPT_FILE: $(OPT_FILE)" + @echo "BLD_DIR: $(BLD_DIR)" + @echo "DIR: $(DIR)" + @echo "TOOLCHAIN: $(toolchain)" + @echo "build: $(BUILD)" + @echo "prefix: $(prefix)" + @echo "generator: $(generator)" + +ver: + @echo $(VERSION) + +variables := $(filter-out toolchain=% generator=% build=% verbose=% prefix=%,$(MAKEOVERRIDES)) +makevars := $(variables:%=-D%) + +envvars += $(shell sed -n '/^ENV:/{s/^ENV://;p;}' $(OPT_FILE) 2>/dev/null) +makevars += $(patsubst %,-D%,$(shell sed -n '/^...:/!{s/ *\#.*$$//; /^$$/!p;}' \ + $(OPT_FILE) 2>/dev/null)) + +makecmd = $(envvars) cmake -H. -B$(DIR) \ + $(if $(findstring $(generator),ninja),-GNinja,-G'Unix Makefiles') \ + $(if $(findstring $(verbose),true on 1),-DCMAKE_VERBOSE_MAKEFILE=true) \ + -DTOOLCHAIN=$(toolchain) \ + -DCMAKE_INSTALL_PREFIX=$(prefix) \ + -DCMAKE_BUILD_TYPE=$(build) $(makevars) + +#------------------------------------------------------------------------------- +# bootstrap target +#------------------------------------------------------------------------------- +bootstrap: | $(DIR) + ifeq "$(generator)" "" + @echo -e "\n\e[1;31mBuild tool not specified!\e[0m\n" && false + else ifeq "$(shell which $(generator) 2>/dev/null)" "" + @echo -e "\n\e[1;31mBuild tool $(generator) not found!\e[0m\n" && false + endif + @echo -e "Options file.....: $(OPT_FILE)" + @echo -e "Build directory..: \e[0;36m$(DIR)\e[0m" + @echo -e "Install directory: \e[0;36m$(prefix)\e[0m" + @echo -e "Build type.......: \e[1;32m$(BUILD)\e[0m" + @echo -e "Command-line vars: $(variables)" + @echo -e "\n-- \e[1;37mUsing $(generator) generator\e[0m\n" + @mkdir -p .build + @rm -f inst + @[ -L build ] && rm -f build || true + @echo $(call makecmd) > $(DIR)/.cmake.log + $(call makecmd) 2>&1 | tee $(DIR)/.cmake.bootstrap.log + @[ ! -d build ] && ln -s $(DIR) build || true + @ln -s $(prefix) inst + @echo "make bootstrap $(MAKEOVERRIDES)" > $(DIR)/.bootstrap + @cp $(DIR)/.bootstrap .build/ + @echo "export PROJECT := $(PROJECT)" > $(DIR)/cache.mk + @echo "export VERSION := $(VERSION)" >> $(DIR)/cache.mk + @echo "export OPT_FILE := $(abspath $(OPT_FILE))" >> $(DIR)/cache.mk + @echo "export generator := $(generator)" >> $(DIR)/cache.mk + @echo "export toolchain := $(toolchain)" >> $(DIR)/cache.mk + @echo "export build := $(BUILD)" >> $(DIR)/cache.mk + @echo "export DIR := $(DIR)" >> $(DIR)/cache.mk + @echo "export prefix := $(prefix)" >> $(DIR)/cache.mk + +$(DIR): + @mkdir -p $@ + +.PHONY: all bootstrap info diff --git a/build-aux/erlang.m4 b/build-aux/erlang.m4 deleted file mode 100644 index c1674c2..0000000 --- a/build-aux/erlang.m4 +++ /dev/null @@ -1,87 +0,0 @@ -dnl------------------------------------------------------------------- -dnl AC_ERLANG_SUBST_ERTS_VER -dnl------------------------------------------------------------------- -AC_DEFUN([AC_ERLANG_SUBST_ERTS_VER], - [AC_REQUIRE([AC_ERLANG_NEED_ERLC])[]dnl - AC_REQUIRE([AC_ERLANG_NEED_ERL])[]dnl - AC_CACHE_CHECK([for Erlang/OTP ERTS version], - [erlang_cv_erts_ver], - [AC_LANG_PUSH(Erlang)[]dnl - AC_RUN_IFELSE( - [AC_LANG_PROGRAM([], [dnl - Version = erlang:system_info(version), - file:write_file("conftest.out", Version), - halt(0)])], - [erlang_cv_erts_ver=`cat conftest.out`], - [AC_MSG_FAILURE([test Erlang program execution failed])]) - AC_LANG_POP(Erlang)[]dnl - ]) - AC_SUBST([ERLANG_ERTS_VER], [$erlang_cv_erts_ver]) - ])# AC_ERLANG_SUBST_ERTS_VER - -dnl------------------------------------------------------------------- -dnl More functions to query Erlang environment. -dnl------------------------------------------------------------------- - -dnl ERLANG_CHECK_ERTS([ACTION-IF-FOUND [, ACTION-IF-NOT-FOUND]]) -dnl Substitudes -dnl ERLANG_ERTS_DIR -dnl ERLANG_ERTS_VER -AC_DEFUN([ERLANG_CHECK_ERTS], -[ -AC_REQUIRE([AC_ERLANG_SUBST_ROOT_DIR])[]dnl -AC_CACHE_CHECK([for Erlang/OTP ERTS version], - [erlang_cv_erts_ver], - [ - AC_LANG_PUSH(Erlang)[]dnl - AC_RUN_IFELSE( - [AC_LANG_PROGRAM([], [dnl - file:write_file("conftest.out", erlang:system_info(version)), - halt(0)])], - [erlang_cv_erts_ver=`cat conftest.out`], - [if test ! -f conftest.out; then - AC_MSG_FAILURE([test Erlang program execution failed]) - else - erlang_cv_erts_ver="not found" - fi]) - AC_LANG_POP(Erlang)[]dnl - ]) -AC_CACHE_CHECK([for Erlang/OTP ERTS directory], - [erlang_cv_erts_dir], - [ - erlang_cv_erts_dir="${ERLANG_ROOT_DIR}/erts-$erlang_cv_erts_ver" - erlang_cv_drv_include="${ERLANG_ROOT_DIR}/usr/include" - if test ! -d "$erlang_cv_erts_dir"; then - erlang_cv_erts_dir="${ERLANG_ROOT_DIR}/usr" - fi - ]) -AC_SUBST([ERLANG_ERTS_DIR], [$erlang_cv_erts_dir]) -AC_SUBST([ERLANG_ERTS_VER], [$erlang_cv_erts_ver]) -AC_SUBST([ERLANG_DRV_INCLUDE], [$erlang_cv_drv_include]) -AS_IF([test "$erlang_cv_erts_ver" = "not found"], [$2], [$1]) -]) - -dnl ERLANG_CHECK_RELEASE() -dnl Substitudes -dnl ERLANG_RELEASE -AC_DEFUN([ERLANG_CHECK_RELEASE], -[ -AC_REQUIRE([AC_ERLANG_SUBST_ROOT_DIR])[]dnl -AC_CACHE_CHECK([for Erlang/OTP release], - [erlang_cv_release], - [ - AC_LANG_PUSH(Erlang)[]dnl - AC_RUN_IFELSE( - [AC_LANG_PROGRAM([], [dnl - file:write_file("conftest.out", erlang:system_info(otp_release)), - halt(0)])], - [erlang_cv_release=`cat conftest.out`], - [if test ! -f conftest.out; then - AC_MSG_FAILURE([test Erlang program execution failed]) - else - erlang_cv_release="not found" - fi]) - AC_LANG_POP(Erlang)[]dnl - ]) -AC_SUBST([ERLANG_RELEASE], [$erlang_cv_release]) -]) diff --git a/build-aux/install-symlinks.cmake b/build-aux/install-symlinks.cmake new file mode 100644 index 0000000..451293b --- /dev/null +++ b/build-aux/install-symlinks.cmake @@ -0,0 +1,11 @@ +# vim:ts=2:sw=2:et + +if(CMAKE_HOST_UNIX) + get_filename_component(Dir ${CMAKE_INSTALL_PREFIX} DIRECTORY) + message(STATUS "Symlink: ${Dir}/current -> ${CMAKE_INSTALL_PREFIX}") + + execute_process( + COMMAND rm -f ${Dir}/current + COMMAND ln -s ${CMAKE_INSTALL_PREFIX} ${Dir}/current + ) +endif() diff --git a/build-aux/license.sh b/build-aux/license.sh new file mode 100644 index 0000000..067f0d7 --- /dev/null +++ b/build-aux/license.sh @@ -0,0 +1,8 @@ +for f in $(find include -name '*.?pp') $(find src -name '*.cpp'); do + echo "Processing $f" + gawk -i inplace ' + BEGIN {m=1} + /BEGIN LICENSE BLOCK/ {m=0; print $0; while (getline < "LICENSE.header") print} + /END LICENSE BLOCK/ {m=1} m {print $0} + ' $f +done diff --git a/config.h.in b/config.h.in new file mode 100644 index 0000000..51233b0 --- /dev/null +++ b/config.h.in @@ -0,0 +1,3 @@ +/* erl_interface/src/epmd/ei_epmd.h is available */ +#cmakedefine HAVE_EI_EPMD +#cmakedefine ALIGNOF_UINT64_T @ALIGNOF_UINT64_T@ diff --git a/configure.ac b/configure.ac deleted file mode 100644 index f24a218..0000000 --- a/configure.ac +++ /dev/null @@ -1,172 +0,0 @@ -# -*- Autoconf -*- - -AC_PREREQ([2.63]) -AC_INIT([eixx], [0.1], [BUG-REPORT-ADDRESS]) -AC_CONFIG_AUX_DIR([build-aux]) -AC_CONFIG_MACRO_DIR([build-aux]) -AM_INIT_AUTOMAKE([foreign -Wall -Wno-portability]) -AM_CONFIG_HEADER(config.h) -AX_PREFIX_CONFIG_H(include/eixx/config.h) - -AC_SUBST([eixxdir], [$libdir]) - -# Default prefix -AC_PREFIX_DEFAULT(`pwd`) -AC_SUBST([default_prefix], [${PWD}/install]) -test "$prefix" = "NONE" && prefix="$default_prefix" -test "$sysconfdir" = "\${prefix}/etc" && sysconfdir='../etc' -test "$scriptdir" = "" && scriptdir='../lib' - -# Options - -AC_ARG_VAR([ERLC_FLAGS], [general flags to prepend to ERLC_FLAGS]) - -ERLC_FLAGS="${ERLC_FLAGS} +debug_info" -# CXXFLAGS="${CXXFLAGS% } -MMD -Wall -fno-strict-aliasing -fpermissive -Wl,-V" -CXXFLAGS="${CXXFLAGS% } -MMD -Wall -fno-strict-aliasing" - -AC_ARG_ENABLE(debug, - AC_HELP_STRING([--enable-debug],[enable debug [[default=no]]]), - [ if test "x$enable_debug" = "xyes" -o -z "x$enable_debug"; then - # CXXFLAGS="${CXXFLAGS% } -ggdb -O0 -Wall -fno-default-inline -fno-inline -fno-inline-functions" - CXXFLAGS="${CXXFLAGS% } -g -O0" - elif test "x$enable_debug" = "xno"; then - ERLC_FLAGS="${ERLC_FLAGS//+debug_info}" - fi - ], -) - -AC_ARG_ENABLE(optimize, - AC_HELP_STRING([--enable-optimize],[enable code optimization [[default=yes]]]), - [ if test "x$enable_optimize" = "xyes" ; then - CXXFLAGS="${CXXFLAGS% } -g -O3" - fi - ], -) - -AC_ARG_ENABLE(warnings, - AC_HELP_STRING([--enable-warnings],[enable Wall compiling [[default=yes]]]), - [ if test "x$enable_warnings" = "xyes" -o -z "$enable_warnings"; then - CXXFLAGS="${CXXFLAGS% } -Wall -Werror" - fi - ], -) - -# libtool -LT_INIT([disable-static]) - -# Checks for programs. -AC_PROG_CXX -AC_PROG_SED - -#AC_CONFIG_HEADERS([config.h]) - -# Checks for libraries. - -AX_BOOST_BASE([1.49.0], [], [AC_MSG_ERROR("BOOST library version >= 1.49.0 not found!")]) -AX_BOOST_SYSTEM -AX_BOOST_THREAD -AX_BOOST_ASIO -AX_BOOST_DATE_TIME -AX_BOOST_UNIT_TEST_FRAMEWORK - -# eixx requires md5 support -AX_LIB_CRYPTO([yes]) -AC_CHECK_HEADERS([openssl/md5.h]) - -# Checks for header files. -AC_CHECK_HEADERS([stdlib.h string.h sys/time.h]) - -# Checks for typedefs, structures, and compiler characteristics. -AC_HEADER_STDBOOL -AC_C_INLINE -AC_TYPE_SIZE_T -AC_TYPE_UINT32_T -AC_TYPE_UINT64_T - -# Checks for library functions. -AC_CHECK_FUNCS([gettimeofday socket sqrt]) - -dnl ------------------------------------------------------------------ -dnl Erlang environment. -dnl ------------------------------------------------------------------ - -AC_MSG_NOTICE([Erlang environment]) - -dnl Check for erl_interface (used by port drivers). - -AC_ERLANG_CHECK_LIB([kernel],, - [AC_MSG_ERROR([kernel was not found!])]) -AC_ERLANG_CHECK_LIB([stdlib],, - [AC_MSG_ERROR([stdlib was not found!])]) -AC_ERLANG_CHECK_LIB([sasl],, - [AC_MSG_ERROR([sasl was not found!])]) -AC_ERLANG_CHECK_LIB([erl_interface],, - [AC_MSG_ERROR([erl_interface was not found!])]) - -AC_SUBST(ERL_CALL,[$ERLANG_LIB_DIR_erl_interface]/bin/erl_call) - -dnl Available flags. -AC_ARG_WITH([erlang], - AC_HELP_STRING([--with-erlang=PATH], - [PATH to Erlang installation (optional)]), - with_erlang=${withval%/}, - with_erlang="") - -dnl erl(1) is used to compile Erlang modules. -if test "x${with_erlang}" = "x"; then - AC_ERLANG_PATH_ERL - AC_ERLANG_PATH_ERLC - AC_ERLANG_SUBST_ROOT_DIR -else - erl_path="${with_erlang}/bin" - AC_ERLANG_PATH_ERL(, [$erl_path$PATH_SEPARATOR$PATH]) - AC_ERLANG_PATH_ERLC(, [$erl_path$PATH_SEPARATOR$PATH]) - AC_ERLANG_SUBST_ROOT_DIR -fi - -if test "x${ERL}" = "x"; then - AC_MSG_ERROR([ -Erlang not found. Fill the ERL variable with erl(1) path or provide -Erlang prefix with --with-erlang.]) -fi - -AC_SUBST([EI_LDFLAGS], ["-L${ERLANG_LIB_DIR_erl_interface}/lib"]) -AC_SUBST([EI_LIB], ["-lei"]) - -AC_CHECK_HEADERS([${ERLANG_LIB_DIR_erl_interface}/src/epmd/ei_epmd.h], - [ AC_DEFINE([HAVE_EI_EPMD], [1], - [erl_interface/src/epmd/ei_epmd.h is available]) - AC_SUBST([EI_CPPFLAGS], - ["-I${ERLANG_LIB_DIR_erl_interface}/include -I${ERLANG_LIB_DIR_erl_interface}/src"]) - ], - [ - AC_SUBST([EI_CPPFLAGS], - ["-I ${ERLANG_LIB_DIR_erl_interface}/include"]) - ]) - -dnl Get ERTS version. -ERLANG_CHECK_ERTS -ERLANG_CHECK_RELEASE -AC_ERLANG_SUBST_ERTS_VER - -dnl ------------------------------------------------------------------ -dnl Output. -dnl ------------------------------------------------------------------ - -AC_CONFIG_FILES([Makefile]) -AC_CONFIG_FILES([src/Makefile]) -AC_CONFIG_FILES([src/eixx.pc:src/eixx.pc.in]) -AC_CONFIG_FILES([test/Makefile]) - -AC_OUTPUT - -echo -echo "========================================================================" -echo " CXXFLAGS: ${CXXFLAGS}" $BOOST_CPPFLAGS -echo " BOOST: $BOOST_ROOT" -echo " Erlang: $ERLANG_ROOT_DIR" -echo "------------------------------------------------------------------------" -echo " Configuration completed successfully! " -echo "========================================================================" -echo diff --git a/eixx.kdev4 b/eixx.kdev4 new file mode 100644 index 0000000..c7bd927 --- /dev/null +++ b/eixx.kdev4 @@ -0,0 +1,3 @@ +[Project] +Manager=KDevCustomMakeManager +Name=eixx diff --git a/eixx.pc.in b/eixx.pc.in new file mode 100644 index 0000000..ee361c2 --- /dev/null +++ b/eixx.pc.in @@ -0,0 +1,13 @@ +prefix=@CMAKE_INSTALL_PREFIX@ +exec_prefix=@CMAKE_INSTALL_PREFIX@ +libdir=@CMAKE_INSTALL_PREFIX@/lib +libsuffix=@LIB_SUFFIX@ +includedir=@CMAKE_INSTALL_PREFIX@/include + +Name: @PROJECT_NAME@ +Description: EIXX: C++ Interface to Erlang +#Requires: boost_1_55_0 +Version: @PROJECT_VERSION@ +Libs: -L${libdir} -L@Erlang_EI_LIBRARY_DIR@ -Wl,-rpath,${libdir} -leixx${libsuffix} -lei -lssl -lcrypto +Cflags: -I${includedir} -I@Erlang_EI_INCLUDE_DIR@ + diff --git a/include/eixx/alloc_pool.hpp b/include/eixx/alloc_pool.hpp index a2867e7..9eddd40 100644 --- a/include/eixx/alloc_pool.hpp +++ b/include/eixx/alloc_pool.hpp @@ -9,23 +9,19 @@ /* ***** BEGIN LICENSE BLOCK ***** -This file is part of the eixx (Erlang C++ Interface) Library. +Copyright 2010 Serge Aleynikov -Copyright (C) 2010 Serge Aleynikov +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. + http://www.apache.org/licenses/LICENSE-2.0 -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. ***** END LICENSE BLOCK ***** */ @@ -33,16 +29,15 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #define _EIXX_ALLOC_POOL_HPP_ #include -#include // definition of EIXX_NAMESPACE #define EIXX_USE_ALLOCATOR -namespace EIXX_NAMESPACE { +namespace eixx { typedef boost::fast_pool_allocator allocator_t; //typedef boost::pool_allocator allocator_t; -} // namespace EIXX_NAMESPACE +} // namespace eixx #endif // _EIXX_ALLOC_POOL_HPP_ diff --git a/include/eixx/alloc_pool_st.hpp b/include/eixx/alloc_pool_st.hpp index e2f3225..4b75c12 100644 --- a/include/eixx/alloc_pool_st.hpp +++ b/include/eixx/alloc_pool_st.hpp @@ -10,23 +10,19 @@ /* ***** BEGIN LICENSE BLOCK ***** -This file is part of the eixx (Erlang C++ Interface) Library. +Copyright 2010 Serge Aleynikov -Copyright (C) 2010 Serge Aleynikov +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. + http://www.apache.org/licenses/LICENSE-2.0 -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. ***** END LICENSE BLOCK ***** */ @@ -35,11 +31,10 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include #include -#include // definition of EIXX_NAMESPACE #define EIXX_USE_ALLOCATOR -namespace EIXX_NAMESPACE { +namespace eixx { typedef boost::fast_pool_allocator< char @@ -47,7 +42,7 @@ typedef boost::fast_pool_allocator< , boost::details::pool::null_mutex > allocator_t; -} // namespace EIXX_NAMESPACE +} // namespace eixx #endif // _EIXX_ALLOC_POOL_HPP_ diff --git a/include/eixx/alloc_std.hpp b/include/eixx/alloc_std.hpp index 8f349c2..a000e3c 100644 --- a/include/eixx/alloc_std.hpp +++ b/include/eixx/alloc_std.hpp @@ -9,23 +9,19 @@ /* ***** BEGIN LICENSE BLOCK ***** -This file is part of the eixx (Erlang C++ Interface) Library. +Copyright 2010 Serge Aleynikov -Copyright (C) 2010 Serge Aleynikov +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. + http://www.apache.org/licenses/LICENSE-2.0 -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. ***** END LICENSE BLOCK ***** */ @@ -33,15 +29,14 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #define _EIXX_ALLOC_STD_HPP_ #include -#include // definition of EIXX_NAMESPACE #define EIXX_USE_ALLOCATOR -namespace EIXX_NAMESPACE { +namespace eixx { typedef std::allocator allocator_t; -} // namespace EIXX_NAMESPACE +} // namespace eixx #endif // _EIXX_ALLOC_STD_HPP_ diff --git a/include/eixx/alloc_std_debug.hpp b/include/eixx/alloc_std_debug.hpp index e59078c..fe781e0 100644 --- a/include/eixx/alloc_std_debug.hpp +++ b/include/eixx/alloc_std_debug.hpp @@ -10,23 +10,19 @@ /* ***** BEGIN LICENSE BLOCK ***** -This file is part of the eixx (Erlang C++ Interface) Library. +Copyright 2010 Serge Aleynikov -Copyright (C) 2010 Serge Aleynikov +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. + http://www.apache.org/licenses/LICENSE-2.0 -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. ***** END LICENSE BLOCK ***** */ @@ -35,11 +31,10 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include #include -#include // definition of EIXX_NAMESPACE #define EIXX_USE_ALLOCATOR -namespace EIXX_NAMESPACE { +namespace eixx { namespace detail { template @@ -95,7 +90,7 @@ struct allocator_t : public detail::debug_allocator }; }; -} // namespace EIXX_NAMESPACE +} // namespace eixx #endif // _EIXX_ALLOC_STD_HPP_ diff --git a/include/eixx/connect.hpp b/include/eixx/connect.hpp index 121a426..b50edd1 100644 --- a/include/eixx/connect.hpp +++ b/include/eixx/connect.hpp @@ -9,23 +9,19 @@ /* ***** BEGIN LICENSE BLOCK ***** -This file is part of the eixx (Erlang C++ Interface) Library. +Copyright 2010 Serge Aleynikov -Copyright (C) 2010 Serge Aleynikov +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. + http://www.apache.org/licenses/LICENSE-2.0 -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. ***** END LICENSE BLOCK ***** */ @@ -37,13 +33,13 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include #include -namespace EIXX_NAMESPACE { +namespace eixx { typedef connect::transport_msg transport_msg; typedef connect::basic_otp_connection otp_connection; typedef connect::basic_otp_mailbox otp_mailbox; typedef connect::basic_otp_node otp_node; -} // namespace EIXX_NAMESPACE +} // namespace eixx #endif diff --git a/include/eixx/connect/basic_otp_connection.hpp b/include/eixx/connect/basic_otp_connection.hpp index 5d939d7..334f0fb 100644 --- a/include/eixx/connect/basic_otp_connection.hpp +++ b/include/eixx/connect/basic_otp_connection.hpp @@ -11,23 +11,19 @@ /* ***** BEGIN LICENSE BLOCK ***** -This file is part of the eixx (Erlang C++ Interface) library. +Copyright 2010 Serge Aleynikov -Copyright (c) 2010 Serge Aleynikov +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. + http://www.apache.org/licenses/LICENSE-2.0 -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. ***** END LICENSE BLOCK ***** */ @@ -35,11 +31,12 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #ifndef _EIXX_BASIC_OTP_CONNECTION_HPP_ #define _EIXX_BASIC_OTP_CONNECTION_HPP_ +#include #include #include #include -namespace EIXX_NAMESPACE { +namespace eixx { namespace connect { template class basic_otp_node; @@ -59,7 +56,7 @@ class basic_otp_connection typename connection_type::pointer m_transport; basic_otp_node* m_node; atom m_remote_nodename; - std::string m_cookie; + atom m_cookie; const Alloc& m_alloc; connect_completion_handler m_on_connect_status; bool m_connected; @@ -68,14 +65,16 @@ class basic_otp_connection bool m_abort; basic_otp_connection( - connect_completion_handler h, - boost::asio::io_service& a_svc, - basic_otp_node* a_node, const atom& a_remote_node, - const std::string& a_cookie, int a_reconnect_secs = 0, - const Alloc& a_alloc = Alloc()) + connect_completion_handler h, + boost::asio::io_service& a_svc, + basic_otp_node* a_node, + atom a_remote_nodename, + atom a_cookie, + int a_reconnect_secs = 0, + const Alloc& a_alloc = Alloc()) : m_io_service(a_svc) , m_node(a_node) - , m_remote_nodename(a_remote_node) + , m_remote_nodename(a_remote_nodename) , m_cookie(a_cookie) , m_alloc(a_alloc) , m_connected(false) @@ -86,17 +85,18 @@ class basic_otp_connection BOOST_ASSERT(a_node != NULL); m_on_connect_status = h; m_transport = connection_type::create( - m_io_service, this, a_node->nodename().to_string(), - a_remote_node.to_string(), a_cookie, a_alloc); + m_io_service, this, a_node->creation(), a_node->nodename(), + a_remote_nodename, a_cookie, a_alloc); } void reconnect() { if (m_abort || m_reconnect_secs <= 0) return; m_reconnect_timer.expires_from_now(boost::posix_time::seconds(m_reconnect_secs)); - m_reconnect_timer.async_wait( - boost::bind(&self::timer_reconnect, this->shared_from_this(), - boost::asio::placeholders::error)); + auto pthis = this->shared_from_this(); + m_reconnect_timer.async_wait([pthis](auto& ec) { + pthis->timer_reconnect(ec); + }); } void timer_reconnect(const boost::system::error_code& ec) { @@ -110,35 +110,36 @@ class basic_otp_connection } m_transport = connection_type::create( - m_io_service, this, m_node->nodename().to_string(), - m_remote_nodename.to_string(), m_cookie, m_alloc); + m_io_service, this, m_node->creation(), m_node->nodename(), + m_remote_nodename, m_cookie, m_alloc); } public: - typedef boost::shared_ptr > pointer; + using pointer = boost::shared_ptr>; boost::asio::io_service& io_service() { return m_io_service; } connection_type* transport() { return m_transport.get(); } verbose_type verbose() const { return m_node->verbose(); } basic_otp_node* node() { return m_node; } - const atom& remote_node() const { return m_remote_nodename; } + atom remote_nodename() const { return m_remote_nodename; } bool connected() const { return m_connected; } int reconnect_timeout() const { return m_reconnect_secs; } /// Set new reconnect timeout in seconds - void reconnect_timeout(size_t a_reconnect_secs) { m_reconnect_secs = a_reconnect_secs; } + void reconnect_timeout(int a_reconnect_secs) { m_reconnect_secs = a_reconnect_secs; } static pointer - connect(connect_completion_handler h, - boost::asio::io_service& a_svc, - basic_otp_node* a_node, const atom& a_remote_node, - const std::string& a_cookie, - int a_reconnect_secs = 0, - const Alloc& a_alloc = Alloc()) + connect(connect_completion_handler h, + boost::asio::io_service& a_svc, + basic_otp_node* a_node, + atom a_remote_nodename, + atom a_cookie, + int a_reconnect_secs = 0, + const Alloc& a_alloc = Alloc()) { pointer p(new basic_otp_connection( - h, a_svc, a_node, a_remote_node, a_cookie, a_reconnect_secs, a_alloc)); + h, a_svc, a_node, a_remote_nodename, a_cookie, a_reconnect_secs, a_alloc)); return p; } @@ -151,12 +152,13 @@ class basic_otp_connection m_transport->stop(); } - void send(const transport_msg& a_msg) throw (err_connection) { + /// @throws err_connection if not connected to \a a_node._ + void send(const transport_msg& a_msg) { if (!m_transport) { if (m_abort) return; else - throw err_connection("Not connected to node", remote_node()); + throw err_connection("Not connected to node", remote_nodename()); } else if (m_connected) m_transport->send(a_msg); // If not connected, the message sending will be ignored @@ -172,7 +174,7 @@ class basic_otp_connection m_on_connect_status(this, std::string()); if (unlikely(verbose() > VERBOSE_NONE)) { report_status(REPORT_INFO, - "Connected to node: " + a_con->remote_node()); + "Connected to node: " + a_con->remote_nodename().to_string()); } } @@ -186,7 +188,8 @@ class basic_otp_connection m_on_connect_status(this, a_error); else if (unlikely(verbose() > VERBOSE_NONE)) { std::stringstream s; - s << "Failed to connect to node " << a_con->remote_node() << ": " << a_error; + s << "Failed to connect to node " + << a_con->remote_nodename() << ": " << a_error; report_status(REPORT_ERROR, s.str()); } reconnect(); @@ -197,12 +200,12 @@ class basic_otp_connection if (unlikely(verbose() > VERBOSE_DEBUG)) { std::stringstream s; - s << "Disconnected from node: " << a_con->remote_node() + s << "Disconnected from node: " << a_con->remote_nodename() << " (" << err.message() << ')'; report_status(REPORT_ERROR, s.str()); } if (m_node) - m_node->on_disconnect_internal(*this, a_con->remote_node(), err); + m_node->on_disconnect_internal(*this, a_con->remote_nodename(), err); m_transport.reset(); reconnect(); @@ -210,12 +213,12 @@ class basic_otp_connection void on_error(connection_type* a_con, const std::string& s) { std::stringstream str; - str << "Error in communication with node: " << a_con->remote_node() + str << "Error in communication with node: " << a_con->remote_nodename() << "\n " << s; report_status(REPORT_ERROR, str.str()); } - void on_message(connection_type* a_con, const transport_msg& a_tm) { + void on_message(connection_type*, const transport_msg& a_tm) { try { m_node->deliver(a_tm); } catch (std::exception& e) { @@ -234,7 +237,7 @@ class basic_otp_connection }; } // namespace connect -} // namespace EIXX_NAMESPACE +} // namespace eixx #endif // _EIXX_BASIC_OTP_CONNECTION_HPP_ diff --git a/include/eixx/connect/basic_otp_mailbox.hpp b/include/eixx/connect/basic_otp_mailbox.hpp index 33b6389..76fde21 100644 --- a/include/eixx/connect/basic_otp_mailbox.hpp +++ b/include/eixx/connect/basic_otp_mailbox.hpp @@ -11,23 +11,19 @@ /* ***** BEGIN LICENSE BLOCK ***** -This file is part of the eixx (Erlang C++ Interface) library. +Copyright 2010 Serge Aleynikov -Copyright (c) 2010 Serge Aleynikov +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. + http://www.apache.org/licenses/LICENSE-2.0 -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. ***** END LICENSE BLOCK ***** */ @@ -36,20 +32,26 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #define _EIXX_BASIC_OTP_MAILBOX_HPP_ #include +#include +#include #include #include #include -#include +#include +#include #include #include -namespace EIXX_NAMESPACE { +namespace eixx { namespace connect { template class basic_otp_node; -using EIXX_NAMESPACE::marshal::list; -using EIXX_NAMESPACE::marshal::epid; +using eixx::marshal::list; +using eixx::marshal::epid; +using eixx::marshal::tuple; +using eixx::marshal::varbind; +using namespace std::chrono; /** * Provides a simple mechanism for exchanging messages with Erlang @@ -87,53 +89,57 @@ template class basic_otp_mailbox { public: - typedef boost::shared_ptr > pointer; + using pointer = boost::shared_ptr>; - typedef boost::function< - void (basic_otp_mailbox&, boost::system::error_code&) + typedef std::function< + bool (basic_otp_mailbox&, transport_msg*&) > receive_handler_type; - typedef std::list*> queue_type; + typedef util::async_queue*, Alloc> queue_type; + + template friend class basic_otp_node; + template friend struct util::async_queue; + template friend class basic_otp_mailbox_registry; + template friend class std::function; + // template friend class std::_Function_handler; private: - boost::asio::io_service& m_io_service; - basic_otp_node& m_node; - epid m_self; - atom m_name; - std::set > m_links; - std::map, epid > m_monitors; - queue_type m_queue; - boost::asio::deadline_timer_ex m_deadline_timer; - boost::function< - void (receive_handler_type f, - boost::system::error_code&)> m_deadline_handler; + boost::asio::io_service& m_io_service; + basic_otp_node& m_node; + epid m_self; + atom m_name; + std::set > m_links; + std::map, epid > m_monitors; + boost::shared_ptr m_queue; + system_clock::time_point m_time_freed; // Cache time of this mbox void do_deliver(transport_msg* a_msg); - void do_on_deadline_timer(receive_handler_type f, boost::system::error_code& ec); + void name(const atom& a_name) { m_name = a_name; } public: basic_otp_mailbox( basic_otp_node& a_node, const epid& a_self, - const atom& a_name = atom(), boost::asio::io_service* a_svc = NULL) + const atom& a_name = atom(), boost::asio::io_service* a_svc = NULL, + const Alloc& a_alloc = Alloc()) + : basic_otp_mailbox(a_node, a_self, a_name, 255, a_svc, a_alloc) + {} + + basic_otp_mailbox( + basic_otp_node& a_node, const epid& a_self, + const atom& a_name = atom(), int a_queue_size = 255, + boost::asio::io_service* a_svc = NULL, const Alloc& a_alloc = Alloc()) : m_io_service(a_svc ? *a_svc : a_node.io_service()) , m_node(a_node), m_self(a_self) , m_name(a_name) - , m_deadline_timer(m_io_service) + , m_queue(new queue_type(m_io_service, a_queue_size, a_alloc)) {} - ~basic_otp_mailbox() { - close(); - } + ~basic_otp_mailbox() { close(); } /// @param a_reg_remove when true the mailbox's pid is removed from registry. /// Only pass false when invoking from the registry on destruction. - void close(const eterm& a_reason = atom("normal"), bool a_reg_remove = true) { - m_deadline_timer.cancel(); - if (a_reg_remove) - m_node.close_mailbox(this); - break_links(a_reason); - } + void close(const eterm& a_reason = am_normal, bool a_reg_remove = true); basic_otp_node& node() const { return m_node; } /// Pid associated with this mailbox. @@ -142,44 +148,108 @@ class basic_otp_mailbox const atom& name() const { return m_name; } boost::asio::io_service& io_service() const { return m_io_service; } /// Queue of pending received messages. - queue_type& queue() { return m_queue; } + //queue_type& queue() { return m_queue; } /// Indicates if mailbox doesn't have any pending messages bool empty() const { return m_queue.empty(); } - void name(const atom& a_name) { m_name = a_name; } + /// Time when this mailbox was placed in the free list + system_clock::time_point time_freed() const { return m_time_freed; } + + /// Register current mailbox under the given name + bool reg(const atom& a_name) { return m_node.register_mailbox(a_name, *this); } bool operator== (const basic_otp_mailbox& rhs) const { return self() == rhs.self(); } bool operator!= (const basic_otp_mailbox& rhs) const { return self() != rhs.self(); } - std::ostream& dump(std::ostream& out) const { - out << "#Mbox{pid=" << self(); - if (m_name != atom()) out << ", name=" << m_name; - return out << '}'; - } + /// Clear mailbox's queue of awaiting messages + void clear() { m_queue->reset(); } + + /// Print pid and regname of the mailbox to the given stream + std::ostream& dump(std::ostream& out) const; + /* /// Find the first message in the mailbox matching a pattern. /// Return the message, and if \a a_binding is not NULL set the binding variables. /// The call is not thread-safe and should be evaluated in the thread running the /// mailbox node's service. transport_msg* match(const eterm& a_pattern, varbind* a_binding = NULL); + */ /// Dequeue the next message from the mailbox. The call is non-blocking and /// returns NULL if no messages are waiting. transport_msg* receive() { - if (m_queue.empty()) - return NULL; - transport_msg* p = m_queue.front(); - m_queue.pop_front(); - return p; + transport_msg* m; + return m_queue->dequeue(m) ? m : nullptr; + } + + /** + * Call a handler on asynchronous delivery of message(s). + * + * The call is non-blocking. If returns Upon timeout + * or delivery of a message to the mailbox the handler \a h will be + * invoked. The handler must have a signature with three arguments: + * \code + * void handler(basic_otp_mailbox& a_mailbox, + * transport_msg*& a_msg, + * ::boost::system::error_code& a_err); + * \endcode + * In case of timeout the error will be set to non-zero value + * + * @param h is the handler to call upon arrival of a message + * @param a_timeout is the timeout interval to wait for message (-1 = infinity) + * @param a_repeat_count is the number of messages to wait (-1 = infinite) + * @return true if the message was synchronously received + * @throws std::runtime_error + **/ + template + bool async_receive + ( + const OnReceive& h, + std::chrono::milliseconds a_timeout = std::chrono::milliseconds(-1), + int a_repeat_count = 0 + ); + + /** + * Cancel pending asynchronous receive operation + */ + void cancel_async_receive() { + m_queue->cancel(); + } + + /** + * Wait for messages and perform pattern match when a message arives + * + * @param a_matcher is the pattern matcher to run + * @param a_on_timeout is the callback to be executed on timeout. It should + * have the following signature + * \code + * void on_timeout(basic_otp_mailbox&); + * \endcode + * @param a_repeat_count number of messages to wait (-1 = infinite) + * @throws std::runtime_error + */ + template + bool async_match + ( + const marshal::eterm_pattern_matcher& a_matcher, + const OnTimeout& a_on_timeout, + std::chrono::milliseconds a_timeout = std::chrono::milliseconds(-1), + int a_repeat_count = 0 + ); + + /// Deliver a message to this mailbox. The call is thread-safe. + void deliver(const transport_msg& a_msg) { + std::unique_ptr> p(new transport_msg(a_msg)); + m_queue->enqueue(p.get()); + p.release(); } /// Deliver a message to this mailbox. The call is thread-safe. - void deliver(const transport_msg& a_msg) { - transport_msg* l_msg = new transport_msg(a_msg); - m_io_service.post( - boost::bind( - &basic_otp_mailbox::do_deliver, this, l_msg)); + void deliver(transport_msg&& a_msg) { + std::unique_ptr> p(new transport_msg(std::move(a_msg))); + m_queue->enqueue(p.get()); + p.release(); } /// Send a message \a a_msg to a pid \a a_to. @@ -195,74 +265,92 @@ class basic_otp_mailbox m_node.send(self(), a_node, a_to, a_msg); } - /** - * Get a message from this mailbox. The call is non-blocking. Upon timeout - * or delivery of a message to the mailbox the handler \a h will be - * invoked. The handler must have a signature with two arguments: - * \verbatim - * void handler(transport_msg& a_msg, boost::system::error_code& ec); - * \endverbatim - * In case of timeout the error will be set to: asio::error::timeout - * that is defined in eixx/util/async_wait_timeout.hpp. - **/ - void async_receive(receive_handler_type h, long msec_timeout = -1) - throw (std::runtime_error); - - /** - * Cancel pending asynchronous receive operation - */ - void cancel_async_receive() { - m_deadline_timer.cancel(); - } - - /** - * Get a message from mailbox that matches the given pattern. - * It will block until an apropiate message arrives. - * @param pattern ErlTerm with pattern to check - * @param binding VariableBinding to use. It can be 0. Default = 0 - * @return an pointer to the ErlTerm representing - * the body of the next message waiting in this mailbox. - * @exception EpiConnectionException if there was an connection error - */ - //bool receive(const eterm& a_pattern, varbind* a_binding = NULL); - /** * Block until response for a RPC call arrives. * @return a pointer to ErlTerm containing the response * @exception EpiConnectionException if there was an connection error - * @throw EpiBadRPC if the corresponding RPC was incorrect + * @throws EpiBadRPC if the corresponding RPC was incorrect */ //bool receive_rpc_reply(const eterm& a_reply); /** - * Send an RPC request to a remote Erlang node. + * Send an RPC request to a remote Erlang node. * @param node remote node where execute the funcion. - * @param mod the name of the Erlang module containing the - * function to be called. - * @param fun the name of the function to call. - * @param args a list of Erlang terms, to be used as arguments - * to the function. + * @param mod the name of the Erlang module containing the + * function to be called. + * @param fun the name of the function to call. + * @param args a list of Erlang terms, to be used as arguments + * to the function. * @throws EpiBadArgument if function, module or nodename are too big * @throws EpiInvalidTerm if any of the args is invalid - * @throws EpiEncodeException if encoding fails + * @throws EpiEncodeException if encoding fails * @throws EpiConnectionException if send fails - */ + */ void send_rpc(const atom& a_node, const atom& a_mod, const atom& a_fun, const list& args, - const epid* gleader = NULL) { + const epid* /*gleader*/ = NULL) { m_node.send_rpc(self(), a_node, a_mod, a_fun, args); } - /// Execute an equivalent of rpc:cast(...). Doesn't return any value. + /// Send an RPC request to a remote Erlang node. + void send_rpc(const atom& a_node, + const std::string& a_mod, + const std::string& a_fun, + const list& args, + const epid* /*gleader*/ = NULL) { + m_node.send_rpc(self(), a_node, atom(a_mod), atom(a_fun), args); + } + + /// Send an RPC request to a remote Erlang node. + void send_rpc(const atom& a_node, + const char* a_mod, + const char* a_fun, + const list& args, + const epid* /*gleader*/ = NULL) { + m_node.send_rpc(self(), a_node, atom(a_mod), atom(a_fun), args); + } + + /** + * Execute an equivalent of rpc:cast(...). Doesn't return any value. + * @throws err_bad_argument + * @throws err_no_process + * @throws err_connection + */ void send_rpc_cast(const atom& a_node, const atom& a_mod, const atom& a_fun, const list& args, const epid* gleader = NULL) - throw (err_bad_argument, err_no_process, err_connection) { + { m_node.send_rpc_cast(self(), a_node, a_mod, a_fun, args, gleader); } + /** + * Execute an equivalent of rpc:cast(...). Doesn't return any value. + * @throws err_bad_argument + * @throws err_no_process + * @throws err_connection + */ + void send_rpc_cast(const atom& a_node, const std::string& a_mod, + const std::string& a_fun, const list& args, + const epid* gleader = NULL) + { + m_node.send_rpc_cast(self(), a_node, atom(a_mod), atom(a_fun), args, gleader); + } + + /** + * Execute an equivalent of rpc:cast(...). Doesn't return any value. + * @throws err_bad_argument + * @throws err_no_process + * @throws err_connection + */ + void send_rpc_cast(const atom& a_node, const char* a_mod, + const char* a_fun, const list& args, + const epid* gleader = NULL) + { + m_node.send_rpc_cast(self(), a_node, atom(a_mod), atom(a_fun), args, gleader); + } + /// Send exit message to all linked pids and monitoring pids void break_links(const eterm& a_reason); @@ -276,7 +364,7 @@ class basic_otp_mailbox /// The given pid will receive an exit message when \a a_pid dies. /// @throws err_no_process /// @throws err_connection - void link(const epid& a_to) throw (err_no_process, err_connection) { + void link(const epid& a_to) { if (self() == a_to) return; if (m_links.find(a_to) != m_links.end()) @@ -293,7 +381,8 @@ class basic_otp_mailbox } /// Set up a monitor of a remote \a a_target_pid. - const ref& monitor(const epid& a_target_pid) { + //const ref& monitor(const epid& a_target_pid) { + void monitor(const epid& a_target_pid) { if (self() == a_target_pid) return; const ref& l_ref = m_node.send_monitor(self(), a_target_pid); @@ -310,148 +399,20 @@ class basic_otp_mailbox } }; -//------------------------------------------------------------------------------ -// basic_otp_mailbox implementation -//------------------------------------------------------------------------------ - -template -void basic_otp_mailbox:: -break_links(const eterm& a_reason) -{ - for (typename std::set >::const_iterator - it=m_links.begin(), end = m_links.end(); it != end; ++it) - try { m_node.send_exit(self(), *it, a_reason); } catch(...) {} - for (typename std::map, epid >::const_iterator - it = m_monitors.begin(), end = m_monitors.end(); it != end; ++it) - try { m_node.send_monitor_exit(self(), it->second, it->first, a_reason); } catch(...) {} - if (!m_links.empty()) m_links.clear(); - if (!m_monitors.empty()) m_monitors.clear(); -} - -template -transport_msg* basic_otp_mailbox:: -match(const eterm& a_pattern, varbind* a_binding) -{ - for (typename queue_type::iterator it = m_queue.begin(), e = m_queue.end(); - it != e; ++it) - { - transport_msg* p = *it; - BOOST_ASSERT(p); - if (a_pattern.match(p->msg(), a_binding)) { - // Found a match - m_queue.erase(it); - return p; - } - } - return NULL; -} - -template -void basic_otp_mailbox:: -do_on_deadline_timer(receive_handler_type f, boost::system::error_code& ec) -{ - m_deadline_timer.expires_at(boost::asio::deadline_timer_ex::time_type()); - - f(*this, ec); // In case of timeout ec would contain boost::asio::error::timeout -} - -template -void basic_otp_mailbox:: -async_receive(receive_handler_type h, long msec_timeout) throw (std::runtime_error) -{ - m_deadline_timer.cancel(); - - // expires_at() == boost::posix_time::not_a_date_time - /* - if (m_deadline_timer.expires_at() != boost::asio::deadline_timer_ex::time_type()) - throw eterm_exception( - "Another receive() is already scheduled for mailbox", self()); - */ - - if (msec_timeout < 0) - m_deadline_timer.async_wait( - boost::bind( - &basic_otp_mailbox::do_on_deadline_timer, - this, h, boost::asio::placeholders::error)); - else - m_deadline_timer.async_wait_timeout( - boost::bind( - &basic_otp_mailbox::do_on_deadline_timer, - this, h, boost::asio::placeholders::error), - msec_timeout); -} - -template -void basic_otp_mailbox:: -do_deliver(transport_msg* a_msg) -{ - try { - switch (a_msg->type()) { - case transport_msg::LINK: - BOOST_ASSERT(a_msg->to_pid() == self()); - m_links.insert(a_msg->from_pid()); - delete a_msg; - return; - - case transport_msg::UNLINK: - BOOST_ASSERT(a_msg->to_pid() == self()); - m_links.erase(a_msg->from_pid()); - delete a_msg; - return; - - case transport_msg::MONITOR_P: - BOOST_ASSERT((a_msg->to().type() == PID && a_msg->to_pid() == self()) - || a_msg->to().to_atom() == m_name); - m_monitors.insert( - std::pair, epid >(a_msg->get_ref(), a_msg->from_pid())); - delete a_msg; - return; - - case transport_msg::DEMONITOR_P: - m_monitors.erase(a_msg->get_ref()); - delete a_msg; - return; - - case transport_msg::MONITOR_P_EXIT: - m_monitors.erase(a_msg->get_ref()); - m_queue.push_back(a_msg); - break; - - case transport_msg::EXIT2: - case transport_msg::EXIT2_TT: - BOOST_ASSERT(a_msg->to_pid() == self()); - m_links.erase(a_msg->from_pid()); - m_queue.push_back(a_msg); - break; - - default: - m_queue.push_back(a_msg); - } - } catch (std::exception& e) { - a_msg->set_error_flag(); - m_queue.push_back(a_msg); - } - - // If the timer's expiration is set to some non-default value, it means that - // there's an outstanding asynchronous receive operation. We cancel the timer - // that will cause invocation of the handler passed to the deadline timer - // upon executing mailbox->async_receive(Handler, Timeout). - if (m_deadline_timer.expires_at() != boost::asio::deadline_timer_ex::time_type()) - m_deadline_timer.cancel(); -} - } // namespace connect -} // namespace EIXX_NAMESPACE +} // namespace eixx namespace std { template ostream& operator<< (ostream& out, - const EIXX_NAMESPACE::connect::basic_otp_mailbox& a_mbox) + const eixx::connect::basic_otp_mailbox& a_mbox) { return a_mbox.dump(out); } } // namespace std +#include + #endif // _EIXX_BASIC_OTP_MAILBOX_HPP_ diff --git a/include/eixx/connect/basic_otp_mailbox.hxx b/include/eixx/connect/basic_otp_mailbox.hxx new file mode 100644 index 0000000..62eee4b --- /dev/null +++ b/include/eixx/connect/basic_otp_mailbox.hxx @@ -0,0 +1,205 @@ +//---------------------------------------------------------------------------- +/// \file basic_otp_mailbox.hxx +//---------------------------------------------------------------------------- +/// \brief Implemention of basic mailbox functionality. +//---------------------------------------------------------------------------- +// Copyright (c) 2010 Serge Aleynikov +// Created: 2010-09-20 +//---------------------------------------------------------------------------- +/* +***** BEGIN LICENSE BLOCK ***** + +Copyright 2010 Serge Aleynikov + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +***** END LICENSE BLOCK ***** +*/ + +#ifndef _EIXX_BASIC_OTP_MAILBOX_IPP_ +#define _EIXX_BASIC_OTP_MAILBOX_IPP_ + +namespace eixx { +namespace connect { + +//------------------------------------------------------------------------------ +// basic_otp_mailbox implementation +//------------------------------------------------------------------------------ + +template +void basic_otp_mailbox:: +close(const eterm& a_reason, bool a_reg_remove) { + m_time_freed = std::chrono::system_clock::now(); + m_queue->reset(); + if (a_reg_remove) + m_node.close_mailbox(this); + break_links(a_reason); + m_name = atom(); +} + +template +template +bool basic_otp_mailbox:: +async_receive(const OnReceive& h, std::chrono::milliseconds a_timeout, + int a_repeat_count) +{ + return m_queue->async_dequeue( + [this, &h](transport_msg*& a_msg, const boost::system::error_code& ec) { + if (this->m_time_freed.time_since_epoch().count() == 0) + return false; + bool res; + if (ec) { + transport_msg* p(nullptr); + res = h(*this, p); + } else { + res = h(*this, a_msg); + if (a_msg) { + delete a_msg; + a_msg = nullptr; + } + } + + return res; + }, + a_timeout, + a_repeat_count); +} + +template +template +bool basic_otp_mailbox:: +async_match(const marshal::eterm_pattern_matcher& a_matcher, + const OnTimeout& a_on_timeout, + std::chrono::milliseconds a_timeout, + int a_repeat_count) +{ + auto f = + [this, &a_matcher, &a_on_timeout] + (transport_msg*& a_msg, const boost::system::error_code& ec) { + if (this->m_time_freed.time_since_epoch().count() == 0) + return false; + if (ec) { + a_on_timeout(*this); + return false; + } + varbind binding; + if (a_msg) { + a_matcher.match(a_msg->msg(), &binding); + delete a_msg; + a_msg = nullptr; + } + return true; + }; + + return m_queue->async_dequeue(f, a_timeout, a_repeat_count); +} + +template +void basic_otp_mailbox:: +break_links(const eterm& a_reason) +{ + for (typename std::set >::const_iterator + it=m_links.begin(), end = m_links.end(); it != end; ++it) + try { m_node.send_exit(self(), *it, a_reason); } catch(...) {} + for (typename std::map, epid >::const_iterator + it = m_monitors.begin(), end = m_monitors.end(); it != end; ++it) + try { m_node.send_monitor_exit(self(), it->second, it->first, a_reason); } + catch(...) {} + if (!m_links.empty()) m_links.clear(); + if (!m_monitors.empty()) m_monitors.clear(); +} + +/* +template +transport_msg* basic_otp_mailbox:: +match(const eterm& a_pattern, varbind* a_binding) +{ + for (typename queue_type::iterator it = m_queue->begin(), e = m_queue->end(); + it != e; ++it) + { + transport_msg* p = *it; + BOOST_ASSERT(p); + if (a_pattern.match(p->msg(), a_binding)) { + // Found a match + m_queue.erase(it); + return p; + } + } + return NULL; +} +*/ + +template +void basic_otp_mailbox:: +do_deliver(transport_msg* a_msg) +{ + try { + switch (a_msg->type()) { + case transport_msg::LINK: + BOOST_ASSERT(a_msg->recipient_pid() == self()); + m_links.insert(a_msg->sender_pid()); + delete a_msg; + return; + + case transport_msg::UNLINK: + BOOST_ASSERT(a_msg->recipient_pid() == self()); + m_links.erase(a_msg->sender_pid()); + delete a_msg; + return; + + case transport_msg::MONITOR_P: + BOOST_ASSERT((a_msg->recipient().type() == PID && a_msg->recipient_pid() == self()) + || a_msg->recipient().to_atom() == m_name); + m_monitors.insert( + std::pair, epid >(a_msg->get_ref(), a_msg->sender_pid())); + delete a_msg; + return; + + case transport_msg::DEMONITOR_P: + m_monitors.erase(a_msg->get_ref()); + delete a_msg; + return; + + case transport_msg::MONITOR_P_EXIT: + m_monitors.erase(a_msg->get_ref()); + m_queue.push_back(a_msg); + break; + + case transport_msg::EXIT2: + case transport_msg::EXIT2_TT: + BOOST_ASSERT(a_msg->recipient_pid() == self()); + m_links.erase(a_msg->sender_pid()); + m_queue.push_back(a_msg); + break; + + default: + m_queue.push_back(a_msg); + } + } catch (std::exception& e) { + a_msg->set_error_flag(); + m_queue.push_back(a_msg); + } +} + +template +std::ostream& basic_otp_mailbox:: +dump(std::ostream& out) const { + out << "#Mbox{pid=" << self(); + if (m_name != atom()) out << ", name=" << m_name; + return out << '}'; +} + +} // namespace connect +} // namespace eixx + +#endif // _EIXX_BASIC_OTP_MAILBOX_IPP_ diff --git a/include/eixx/connect/basic_otp_mailbox_registry.hpp b/include/eixx/connect/basic_otp_mailbox_registry.hpp index 855125c..985c7cd 100644 --- a/include/eixx/connect/basic_otp_mailbox_registry.hpp +++ b/include/eixx/connect/basic_otp_mailbox_registry.hpp @@ -10,23 +10,19 @@ /* ***** BEGIN LICENSE BLOCK ***** -This file is part of the eixx (Erlang C++ Interface) library. +Copyright 2010 Serge Aleynikov -Copyright (c) 2010 Serge Aleynikov +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. + http://www.apache.org/licenses/LICENSE-2.0 -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. ***** END LICENSE BLOCK ***** */ @@ -37,26 +33,34 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include #include #include +#include -namespace EIXX_NAMESPACE { +namespace eixx { namespace connect { using detail::lock_guard; template -class basic_otp_mailbox_registry { - basic_otp_node& m_owner_node; - mutable Mutex m_lock; - - typedef basic_otp_mailbox mailbox_type; - typedef mailbox_type* mailbox_ptr; +struct basic_otp_mailbox_registry { + typedef basic_otp_mailbox mailbox_type; + typedef mailbox_type* mailbox_ptr; +private: + basic_otp_node& m_owner_node; // These are made mutable so that intrinsic cleanup is possible // of orphant entries. - mutable std::map m_by_name; - mutable std::map, mailbox_ptr> m_by_pid; + mutable Mutex m_lock; + mutable std::map m_by_name; + mutable std::map, mailbox_ptr> m_by_pid; + + // Cache of freed mailboxes + static std::queue s_free_list; + static Mutex s_free_list_lock; + public: - basic_otp_mailbox_registry(basic_otp_node& a_owner) : m_owner_node(a_owner) {} + basic_otp_mailbox_registry(basic_otp_node& a_owner) + : m_owner_node(a_owner) + {} ~basic_otp_mailbox_registry() { clear(); @@ -67,35 +71,39 @@ class basic_otp_mailbox_registry { void clear(); - bool add(const atom& a_name, mailbox_ptr a_mbox); + bool add(atom a_name, mailbox_ptr a_mbox); /// Unregister a name so that no mailbox is any longer associated with \a a_name. - bool unregister(const atom& a_name); + bool erase(const atom& a_name); /// Remove \a a_mbox mailbox from the registry void erase(mailbox_ptr a_mbox); /** * Look up a mailbox based on its name or pid. + * @throws err_bad_argument + * @throws err_no_process */ mailbox_ptr - get(const eterm& a_proc) const throw (err_bad_argument, err_no_process); + get(const eterm& a_proc) const; /** * Look up a mailbox based on its name. If the mailbox has gone out * of scope we also remove the reference from the hashtable so we * don't find it again. + * @throws err_no_process */ mailbox_ptr - get(const atom& a_name) const throw(err_no_process); + get(atom a_name) const; /** * Look up a mailbox based on its pid. If the mailbox has gone out * of scope we also remove the reference from the hashtable so we * don't find it again. + * @throws err_no_process */ mailbox_ptr - get(const epid& a_pid) const throw(err_no_process); + get(const epid& a_pid) const; void names(std::list& list); @@ -107,157 +115,9 @@ class basic_otp_mailbox_registry { size_t count() const { return m_by_pid.size(); } }; -//------------------------------------------------------------------------------ -// otp_mailbox_registry implementation -//------------------------------------------------------------------------------ - - -template -typename basic_otp_mailbox_registry::mailbox_ptr -basic_otp_mailbox_registry::create_mailbox( - const atom& a_name, boost::asio::io_service* a_svc) -{ - lock_guard guard(m_lock); - if (!a_name.empty()) { - typename std::map::iterator it = m_by_name.find(a_name); - if (it != m_by_name.end()) - return it->second; // Already registered! - } - - epid l_pid = m_owner_node.create_pid(); - mailbox_ptr mbox = new mailbox_type(m_owner_node, l_pid, a_name, a_svc); - if (!a_name.empty()) - m_by_name.insert(std::pair(a_name, mbox)); - m_by_pid.insert(std::pair, mailbox_ptr>(l_pid, mbox)); - return mbox; -} - -template -void basic_otp_mailbox_registry::clear() -{ - if (!m_by_name.empty() || !m_by_pid.empty()) { - static const atom s_am_normal("normal"); - lock_guard guard(m_lock); - m_by_name.clear(); - typename std::map, mailbox_ptr>::iterator it; - for(it = m_by_pid.begin(); it != m_by_pid.end(); ++it) { - mailbox_ptr p = it->second; - p->close(s_am_normal, false); - } - m_by_pid.clear(); - } -} - -template -bool basic_otp_mailbox_registry::add(const atom& a_name, mailbox_ptr a_mbox) -{ - if (a_name.empty()) - throw err_bad_argument("Empty registering name!"); - if (!a_mbox->name().empty()) - throw err_bad_argument("Mailbox already registered as", a_mbox->name()); - lock_guard guard(m_lock); - if (m_by_name.find(a_name) != m_by_name.end()) - return false; - m_by_name.insert(std::pair(a_name, a_mbox)); - a_mbox->name(a_name); - return true; -} - -/// Unregister a name so that no mailbox is any longer associated with \a a_name. -template -bool basic_otp_mailbox_registry::unregister(const atom& a_name) -{ - if (!a_name.empty()) - return false; - lock_guard guard(m_lock); - typename std::map::iterator it = m_by_name.find(a_name); - if (it == m_by_name.end()) - return; - it->second.name(""); - m_by_name.erase(it); -} - -/// Remove \a a_mbox mailbox from the registry -template -void basic_otp_mailbox_registry::erase(mailbox_ptr a_mbox) -{ - if (!a_mbox) - return; - lock_guard guard(m_lock); - m_by_pid.erase(a_mbox->self()); - if (!a_mbox->name().empty()) - m_by_name.erase(a_mbox->name()); - a_mbox->name(""); -} - -/** - * Look up a mailbox based on its name or pid. - */ -template -typename basic_otp_mailbox_registry::mailbox_ptr -basic_otp_mailbox_registry::get(const eterm& a_proc) const - throw (err_bad_argument, err_no_process) -{ - switch (a_proc.type()) { - case ATOM: return get(a_proc.to_atom()); - case PID: return get(a_proc.to_pid()); - default: throw err_bad_argument("Unknown process identifier", a_proc); - } -} - -/** - * Look up a mailbox based on its name. If the mailbox has gone out - * of scope we also remove the reference from the hashtable so we - * don't find it again. - */ -template -typename basic_otp_mailbox_registry::mailbox_ptr -basic_otp_mailbox_registry::get(const atom& a_name) const - throw(err_no_process) -{ - lock_guard guard(m_lock); - typename std::map::iterator it = m_by_name.find(a_name); - if (it != m_by_name.end()) - return it->second; - throw err_no_process("Process not registered", a_name); -} - -/** - * Look up a mailbox based on its pid. If the mailbox has gone out - * of scope we also remove the reference from the hashtable so we - * don't find it again. - */ -template -typename basic_otp_mailbox_registry::mailbox_ptr -basic_otp_mailbox_registry::get(const epid& a_pid) const - throw(err_no_process) -{ - lock_guard guard(m_lock); - typename std::map, mailbox_ptr>::iterator it = m_by_pid.find(a_pid); - if (it != m_by_pid.end()) - return it->second; - throw err_no_process("Process not found", a_pid); -} - -template -void basic_otp_mailbox_registry::names(std::list& list) -{ - lock_guard guard(m_lock); - for(typename std::map::const_iterator - it = m_by_name.begin(), end = m_by_name.end(); it != end; ++it) - list.push_back(it->first); -} - -template -void basic_otp_mailbox_registry::pids(std::list >& list) -{ - lock_guard guard(m_lock); - for(typename std::map, mailbox_ptr>::const_iterator - it = m_by_pid.begin(), end = m_by_pid.eend(); it != end; ++it) - list.push_back(it->first); -} - } // namespace connect -} // namespace EIXX_NAMESPACE +} // namespace eixx + +#include #endif // _EIXX_BASIC_OTP_MAILBOX_REGISTRTY_HPP_ diff --git a/include/eixx/connect/basic_otp_mailbox_registry.hxx b/include/eixx/connect/basic_otp_mailbox_registry.hxx new file mode 100644 index 0000000..663dcf1 --- /dev/null +++ b/include/eixx/connect/basic_otp_mailbox_registry.hxx @@ -0,0 +1,231 @@ +//---------------------------------------------------------------------------- +/// \file basic_otp_mailbox_registry.hxx +//---------------------------------------------------------------------------- +/// \brief Implemention details of mailbox registration functionality +//---------------------------------------------------------------------------- +// Copyright (c) 2010 Serge Aleynikov +// Created: 2010-09-20 +//---------------------------------------------------------------------------- +/* +***** BEGIN LICENSE BLOCK ***** + +Copyright 2010 Serge Aleynikov + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +***** END LICENSE BLOCK ***** +*/ + +#ifndef _EIXX_BASIC_OTP_MAILBOX_REGISTRTY_IPP_ +#define _EIXX_BASIC_OTP_MAILBOX_REGISTRTY_IPP_ +#include + +namespace eixx { +namespace connect { + +//----------------------------------------------------------------------------- +// Static members instantiation +//----------------------------------------------------------------------------- +template +std::queue::mailbox_ptr> +basic_otp_mailbox_registry::s_free_list; + +template +Mutex +basic_otp_mailbox_registry::s_free_list_lock; + +//----------------------------------------------------------------------------- +// Member functions implementation +//----------------------------------------------------------------------------- + +template +typename basic_otp_mailbox_registry::mailbox_ptr +basic_otp_mailbox_registry:: +create_mailbox(const atom& a_name, boost::asio::io_service* a_svc) +{ + static const std::chrono::seconds s_min_retention(180); + + lock_guard guard(m_lock); + if (!a_name.empty()) { + typename std::map::iterator it = m_by_name.find(a_name); + if (it != m_by_name.end()) + return it->second; // Already registered! + } + + mailbox_ptr p = nullptr; + + // Mailboxes are cached, so that they can be reused at some later point + // to prevent overflowing the max number of available pid numbers + { + lock_guard g(s_free_list_lock); + if (!s_free_list.empty() && + std::chrono::duration_cast( + std::chrono::system_clock::now() - + s_free_list.back()->time_freed()) > s_min_retention) + { + p = s_free_list.back(); + s_free_list.pop(); + p->name(a_name); + } + } + + if (p == nullptr) { + epid l_pid = m_owner_node.create_pid(); + p = new mailbox_type(m_owner_node, l_pid, a_name, a_svc); + } + + if (!a_name.empty()) + m_by_name.insert(std::pair(a_name, p)); + m_by_pid.insert(std::pair, mailbox_ptr>(p->self(), p)); + return p; +} + +template +void basic_otp_mailbox_registry:: +clear() +{ + if (!m_by_name.empty() || !m_by_pid.empty()) { + lock_guard guard(m_lock); + m_by_name.clear(); + typename std::map, mailbox_ptr>::iterator it; + for(it = m_by_pid.begin(); it != m_by_pid.end(); ++it) { + mailbox_ptr p = it->second; + p->close(am_normal, false); + } + m_by_pid.clear(); + } +} + +template +bool basic_otp_mailbox_registry:: +add(atom a_name, mailbox_ptr a_mbox) +{ + if (a_name.empty()) + throw err_bad_argument("Empty registering name!"); + if (!a_mbox->name().empty()) + throw err_bad_argument("Mailbox already registered as", a_mbox->name()); + lock_guard guard(m_lock); + auto it = m_by_name.insert(std::pair(a_name, a_mbox)); + if (it.second) + a_mbox->name(a_name); + return it.second; +} + +/// Unregister a name so that no mailbox is any longer associated with \a a_name. +template +bool basic_otp_mailbox_registry:: +erase(const atom& a_name) +{ + if (!a_name.empty()) + return false; + lock_guard guard(m_lock); + typename std::map::iterator it = m_by_name.find(a_name); + if (it == m_by_name.end()) + return false; + it->second.name(atom()); + m_by_name.erase(it); + return true; +} + +/// Remove \a a_mbox mailbox from the registry +template +void basic_otp_mailbox_registry:: +erase(mailbox_ptr a_mbox) +{ + if (!a_mbox) + return; + lock_guard guard(m_lock); + m_by_pid.erase(a_mbox->self()); + if (!a_mbox->name().empty()) + m_by_name.erase(a_mbox->name()); + a_mbox->name(atom()); +} + +/** + * Look up a mailbox based on its name or pid. + * @throws err_bad_argument + * @throws err_no_process + */ +template +typename basic_otp_mailbox_registry::mailbox_ptr +basic_otp_mailbox_registry:: +get(const eterm& a_proc) const +{ + switch (a_proc.type()) { + case ATOM: return get(a_proc.to_atom()); + case PID: return get(a_proc.to_pid()); + default: throw err_bad_argument("Unknown process identifier", a_proc); + } +} + +/** + * Look up a mailbox based on its name. If the mailbox has gone out + * of scope we also remove the reference from the hashtable so we + * don't find it again. + * @throws err_no_process + */ +template +typename basic_otp_mailbox_registry::mailbox_ptr +basic_otp_mailbox_registry:: +get(atom a_name) const +{ + lock_guard guard(m_lock); + typename std::map::iterator it = m_by_name.find(a_name); + if (it != m_by_name.end()) + return it->second; + throw err_no_process("Process not registered", a_name); +} + +/** + * Look up a mailbox based on its pid. If the mailbox has gone out + * of scope we also remove the reference from the hashtable so we + * don't find it again. + * @throws err_no_process + */ +template +typename basic_otp_mailbox_registry::mailbox_ptr +basic_otp_mailbox_registry::get(const epid& a_pid) const +{ + lock_guard guard(m_lock); + typename std::map, mailbox_ptr>::iterator it = m_by_pid.find(a_pid); + if (it != m_by_pid.end()) + return it->second; + throw err_no_process("Process not found", a_pid); +} + +template +void basic_otp_mailbox_registry::names(std::list& list) +{ + list.clear(); + lock_guard guard(m_lock); + list.resize(m_by_name.size()); + for(typename std::map::const_iterator + it = m_by_name.begin(), end = m_by_name.end(); it != end; ++it) + list.push_back(it->first); +} + +template +void basic_otp_mailbox_registry::pids(std::list >& list) +{ + list.clear(); + lock_guard guard(m_lock); + list.resize(m_by_pid.size()); + for(typename std::map, mailbox_ptr>::const_iterator + it = m_by_pid.begin(), end = m_by_pid.eend(); it != end; ++it) + list.push_back(it->first); +} + +} // namespace connect +} // namespace eixx + +#endif // _EIXX_BASIC_OTP_MAILBOX_REGISTRTY_IPP_ diff --git a/include/eixx/connect/basic_otp_node.hpp b/include/eixx/connect/basic_otp_node.hpp index 16a6cf8..9c0541d 100644 --- a/include/eixx/connect/basic_otp_node.hpp +++ b/include/eixx/connect/basic_otp_node.hpp @@ -13,23 +13,19 @@ /* ***** BEGIN LICENSE BLOCK ***** -This file is part of the eixx (Erlang C++ Interface) library. +Copyright 2010 Serge Aleynikov -Copyright (c) 2010 Serge Aleynikov +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. + http://www.apache.org/licenses/LICENSE-2.0 -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. ***** END LICENSE BLOCK ***** */ @@ -37,6 +33,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #ifndef _EIXX_BASIC_OTP_NODE_HPP_ #define _EIXX_BASIC_OTP_NODE_HPP_ +#include #include #include #include @@ -47,10 +44,10 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include #include -namespace EIXX_NAMESPACE { +namespace eixx { namespace connect { -using namespace EIXX_NAMESPACE; +using namespace eixx; using marshal::epid; using marshal::port; using marshal::ref; @@ -78,41 +75,24 @@ class basic_otp_node: public basic_otp_node_local { typedef transport_msg transport_msg_t; typedef basic_otp_connection connection_t; - typedef EIXX_NAMESPACE::detail::hash_map_base< + typedef eixx::detail::hash_map_base< atom, typename connection_t::pointer, atom_con_hash_fun > conn_hash_map; - class atom_con_hash_fun { - conn_hash_map& map; - - static size_t init_default_hash_size() { - const char* p = getenv("EI_MAX_NODE_CONNECTIONS"); - int n = (p && p[0]) ? atoi(p) : 1024; - if (n < 0 || n >= 64*1024) n = 1024; - return n; - } - public: - static size_t get_default_hash_size() { - static const size_t s_max_node_connections = init_default_hash_size(); - return s_max_node_connections; - } - - atom_con_hash_fun(conn_hash_map* a_map) : map(*a_map) {} - - size_t operator()(const atom& data) const { - return data.index() % map.bucket_count(); - } - }; - - uint8_t m_creation; - uint32_t m_pid_count; - uint32_t m_port_count; - uint32_t m_serial; - uint32_t m_refid[3]; + class rpc_server; - boost::asio::io_service& m_io_service; Mutex m_lock; - Mutex m_inc_lock; + uint32_t m_creation; + std::atomic_uint_fast32_t m_pid_count; + std::atomic_uint_fast64_t m_port_count; + std::atomic_uint_fast64_t m_refid0; + #ifdef NEWER_REFERENCE_EXT + std::atomic_uint_fast64_t m_refid1; + #else + std::atomic_uint m_refid1; + #endif + + boost::asio::io_service& m_io_service; basic_otp_mailbox_registry m_mailboxes; conn_hash_map m_connections; Alloc m_allocator; @@ -121,26 +101,30 @@ class basic_otp_node: public basic_otp_node_local { friend class basic_otp_connection; void on_disconnect_internal(const connection_t& a_con, - const std::string& a_remote_node, const boost::system::error_code& err) - { - if (on_disconnect) - on_disconnect(*this, a_con, a_remote_node, err); - } + atom a_remote_nodename, const boost::system::error_code& err); - void report_status(report_level a_level, const connection_t* a_con, const std::string& s); + void report_status(report_level a_level, + const connection_t* a_con, const std::string& s); + void rpc_call(const epid& a_from, const ref& a_ref, + const atom& a_mod, const atom& a_fun, const list& a_args, + const eterm& a_gleader); protected: /// Publish the node port to epmd making this node known to the world. - void publish_port() throw (err_connection); + /// @throws err_connection + void publish_port(); /// Unregister this node from epmd. - void unpublish_port() throw (err_connection); + /// @throws err_connection + void unpublish_port(); - /// Send a message to a process ToProc which is either epid or + /// Send a message to a process ToProc which is either epid or /// atom for registered names. + /// @throws err_no_process + /// @throws err_connection template - void send(const atom& a_to_node, ToProc a_to, const transport_msg& a_msg) - throw (err_no_process, err_connection); + void send(const atom& a_to_node, + ToProc a_to, const transport_msg& a_msg); public: typedef basic_otp_mailbox_registry mailbox_registry_t; @@ -151,7 +135,7 @@ class basic_otp_node: public basic_otp_node_local { * hostname and port. ex: "ei:mynode@host.somewhere.com:3128" * @param a_cookie cookie to use * @param a_alloc is the allocator to use - * @param a_creation is the creation value to use (0 .. 2). This + * @param a_creation is the creation value to use (0 .. 2). This * argument is provided for being able to do determinitic testing. * In production pass the default value, so that the creation value * is determined automatically. @@ -160,20 +144,15 @@ class basic_otp_node: public basic_otp_node_local { * @throws eterm_exception if there is an error in transport creation */ basic_otp_node(boost::asio::io_service& a_io_svc, - const atom& a_nodename = atom(), - const std::string& a_cookie = "", + const std::string& a_nodename = std::string(), + const std::string& a_cookie = std::string(), const Alloc& a_alloc = Alloc(), - int8_t a_creation = -1) - throw (err_bad_argument, err_connection, eterm_exception); + int8_t a_creation = -1); virtual ~basic_otp_node() { close(); } /// Change name of current node - void set_nodename(const atom& a_nodename, const std::string& a_cookie = "") { - close(); - if (a_nodename != atom()) - basic_otp_node_local::set_nodename(a_nodename.to_string(), a_cookie); - } + void set_nodename(const atom& a_nodename, const std::string& a_cookie = ""); /// Get current verboseness level verbose_type verbose() const { return m_verboseness; } @@ -185,16 +164,13 @@ class basic_otp_node: public basic_otp_node_local { /// Get the service object used by this node. boost::asio::io_service& io_service() { return m_io_service; } + /// Run the node's service dispatch void run() { m_io_service.run(); } + /// Stop the node's service dispatch void stop() { m_io_service.stop(); } - void close() { - m_mailboxes.clear(); - for(typename conn_hash_map::iterator - it = m_connections.begin(), end = m_connections.end(); it != end; ++it) - it->second->disconnect(); - m_connections.clear(); - } + /// Close all connections and empty the mailbox + void close(); /** * Create a new mailbox with a new mailbox that can be used to send and @@ -206,16 +182,9 @@ class basic_otp_node: public basic_otp_node_local { * @return new mailbox with a new pid. */ basic_otp_mailbox* - create_mailbox(const atom& a_reg_name = atom(), boost::asio::io_service* a_svc = NULL) { - boost::asio::io_service* p_svc = a_svc ? a_svc : &m_io_service; - return m_mailboxes.create_mailbox(a_reg_name, p_svc); - } + create_mailbox(const atom& a_name = atom(), boost::asio::io_service* a_svc = NULL); - void close_mailbox(basic_otp_mailbox* a_mbox) { - if (a_mbox) { - m_mailboxes.erase(a_mbox); - } - } + void close_mailbox(basic_otp_mailbox* a_mbox); /// Get a mailbox associated with a given atom name or epid. /// @param a_proc is either an atom name of a registered local process or epid. @@ -226,13 +195,16 @@ class basic_otp_node: public basic_otp_node_local { /// Get a mailbox registered by a given atom name. basic_otp_mailbox* - get_mailbox(const atom& a_name) const { return m_mailboxes.get(a_name); } + get_mailbox(atom a_name) const { return m_mailboxes.get(a_name); } /// Get a mailbox registered by a given epid. basic_otp_mailbox* get_mailbox(const epid& a_pid) const { return m_mailboxes.get(a_pid); } - const mailbox_registry_t& registry() const { return m_mailboxes; } + const mailbox_registry_t& registry() const { return m_mailboxes; } + + /// Register mailbox by given name + bool register_mailbox(const atom& a_name, basic_otp_mailbox& a_mbox); /// Create a new unique pid epid create_pid(); @@ -244,7 +216,7 @@ class basic_otp_node: public basic_otp_node_local { ref create_ref(); /// Get creation number - uint8_t creation() const { return m_creation; } + uint32_t creation() const { return m_creation; } /** * Set up a connection to an Erlang node, using given cookie @@ -254,11 +226,11 @@ class basic_otp_node: public basic_otp_node_local { * attempts in case of connection drops. * @returns A new connection. The connection has no receiver defined * and is not started. + * @throws err_connection */ template void connect(CompletionHandler h, const atom& a_remote_node, - const std::string& a_cookie = "", size_t a_reconnect_secs = 0) - throw(err_connection); + const atom& a_cookie = atom(), int a_reconnect_secs = 0); /** * Set up a connection to an Erlang node, using default cookie @@ -267,47 +239,45 @@ class basic_otp_node: public basic_otp_node_local { * attempts in case of connection drops. * @returns A new connection. The connection has no receiver defined * and is not started. + * @throws err_connection */ template - void connect(CompletionHandler h, const atom& a_remote_node, size_t a_reconnect_secs = 0) - throw(err_connection) - { - connect(h, a_remote_node, "", a_reconnect_secs); - } + void connect(CompletionHandler h, const atom& a_remote_nodename, + int a_reconnect_secs = 0); /// Get connection identified by the \a a_node name. /// @throws err_connection if not connected to \a a_node._ - connection_t& connection(const atom& a_nodename) const { - typename conn_hash_map::const_iterator l_con = m_connections.find(a_nodename); - if (l_con == m_connections.end()) - throw err_connection("Not connected to node", a_nodename); - return *l_con->second.get(); - } + connection_t& connection(atom a_nodename) const; /** * Callback invoked on disconnect from a peer node */ boost::function< - // OtpNode OtpConnection RemoteNodeName ErrorCode - void (self&, const connection_t&, const std::string&, const boost::system::error_code&) - > on_disconnect; + // OtpNode OtpConnection RemoteNodeName ErrorCode + void (self&, const connection_t&, atom, const boost::system::error_code&) + > on_disconnect; /** * Callback invoked if verbosity is different from VERBOSE_NONE. If not assigned, - * the content is printed to stderr. + * the content is printed to stderr. */ boost::function< // OtpNode OtpConnection Status Message void (self&, const connection_t*, report_level, const std::string&) - > on_status; + > on_status; + boost::function< + eterm (const epid& a_from, const ref& a_ref, + const atom& a_mod, const atom& a_fun, const list& a_args, + const eterm& a_gleader) + > on_rpc_call; /** * Accept connections from client processes. * This method sets the socket listener for incoming connections and * registers the port with local epmd daemon. * @throws err_connection if cannot connect to epmd. */ - void start_server() throw(err_connection); + void start_server(); /** * Stop accepting connections from client processes. @@ -317,31 +287,38 @@ class basic_otp_node: public basic_otp_node_local { void stop_server(); /// Deliver a message to its local receipient mailbox. - void deliver(const transport_msg& a_tm) - throw (err_bad_argument, err_no_process, err_connection); + /// @throws err_bad_argument + /// @throws err_no_process + /// @throws err_connection + void deliver(const transport_msg& a_tm); /// Send a message \a a_msg from \a a_from pid to \a a_to pid. /// @param a_to is a remote process. /// @param a_msg is the message to send. - void send(const epid& a_to, const eterm& a_msg) - throw (err_no_process, err_connection); + /// @throws err_no_process + /// @throws err_connection + void send(const epid& a_to, const eterm& a_msg); /// Send a message \a a_msg to the remote process \a a_to on node \a a_node. /// The remote process \a a_to need not belong to node \a a_node. /// @param a_node is the node to send the message to. /// @param a_to is a remote process. /// @param a_msg is the message to send. - void send(const atom& a_node, const epid& a_to, const eterm& a_msg) - throw (err_no_process, err_connection); + /// @throws err_no_process + /// @throws err_connection + void send(const atom& a_node, const epid& a_to, const eterm& a_msg); /// Send a message \a a_msg to the local process registered as \a a_to. - void send(const epid& a_from, const atom& a_to, const eterm& a_msg) - throw (err_no_process, err_connection); + /// @throws err_no_process + /// @throws err_connection + void send(const epid& a_from, const atom& a_to, const eterm& a_msg); /// Send a message \a a_msg to the process registered as \a a_to_name /// on remote node \a a_node. + /// @throws err_no_process + /// @throws err_connection void send(const epid& a_from, const atom& a_to_node, const atom& a_to_name, - const eterm& a_msg) throw (err_no_process, err_connection); + const eterm& a_msg); /** * Send an RPC request to a remote Erlang node. @@ -360,53 +337,62 @@ class basic_otp_node: public basic_otp_node_local { */ void send_rpc(const epid& a_from, const atom& a_to_node, const atom& a_mod, const atom& a_fun, const list& args, - const epid* gleader = NULL) - throw (err_bad_argument, err_no_process, err_connection); + const epid* gleader = NULL); /// Execute an equivalent of rpc:cast(...). Doesn't return any value. + /// @throws err_bad_argument + /// @throws err_no_process + /// @throws err_connection void send_rpc_cast(const epid& a_from, const atom& a_to_node, const atom& a_mod, const atom& a_fun, const list& args, - const epid* gleader = NULL) - throw (err_bad_argument, err_no_process, err_connection); + const epid* gleader = NULL); /// Attempt to kill a remote process by sending /// an exit message to a_pid, with reason \a a_reason + /// @throws err_no_process + /// @throws err_connection void send_exit(const epid& a_from, const epid& a_to, - const eterm& a_reason) throw (err_no_process, err_connection); + const eterm& a_reason); /// Attempt to kill a remote process by sending /// an exit2 message to a_pid, with reason \a a_reason + /// @throws err_no_process + /// @throws err_connection void send_exit2(const epid& a_from, const epid& a_to, - const eterm& a_reason) throw (err_no_process, err_connection); + const eterm& a_reason); /// Link mailbox to the given pid. /// The given pid will receive an exit message when \a a_pid dies. /// @throws err_no_process /// @throws err_connection - void send_link(const epid& a_from, const epid& a_to) - throw (err_no_process, err_connection); + void send_link(const epid& a_from, const epid& a_to); /// UnLink the given pid - void send_unlink(const epid& a_from, const epid& a_to) - throw (err_no_process, err_connection); + /// @throws err_no_process + /// @throws err_connection + void send_unlink(const epid& a_from, const epid& a_to); + /// @throws err_no_process + /// @throws err_connection const ref& - send_monitor(const epid& a_from, const epid& a_to_pid) - throw (err_no_process, err_connection); + send_monitor(const epid& a_from, const epid& a_to_pid); /// Demonitor the \a a_to pid monitored by \a a_from pid using \a a_ref reference. - void send_demonitor(const epid& a_from, const epid& a_to, const ref& a_ref) - throw (err_no_process, err_connection); + /// @throws err_no_process + /// @throws err_connection + void send_demonitor(const epid& a_from, const epid& a_to, const ref& a_ref); + /// @throws err_no_process + /// @throws err_connection void send_monitor_exit(const epid& a_from, const epid& a_to, - const ref& a_ref, const eterm& a_reason) - throw (err_no_process, err_connection); + const ref& a_ref, const eterm& a_reason); }; } // namespace connect -} // namespace EIXX_NAMESPACE +} // namespace eixx -#include +#include +#include #endif // _EIXX_BASIC_OTP_NODE_HPP_ diff --git a/include/eixx/connect/basic_otp_node.hxx b/include/eixx/connect/basic_otp_node.hxx new file mode 100644 index 0000000..e4fc8d2 --- /dev/null +++ b/include/eixx/connect/basic_otp_node.hxx @@ -0,0 +1,470 @@ +//---------------------------------------------------------------------------- +/// \file basic_otp_mailbox.hxx +//---------------------------------------------------------------------------- +/// \brief Implementation of mailbox interface functions. +//---------------------------------------------------------------------------- +// Copyright (c) 2010 Serge Aleynikov +// Created: 2010-09-20 +//---------------------------------------------------------------------------- +/* +***** BEGIN LICENSE BLOCK ***** + +Copyright 2010 Serge Aleynikov + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +***** END LICENSE BLOCK ***** +*/ + +#include +#include +#include +#include +#include + +namespace eixx { +namespace connect { + +namespace { + using namespace std::placeholders; + using marshal::var; +} + +//----------------------------------------------------------------------------- +// basic_otp_node::atom_con_hash_fun +//----------------------------------------------------------------------------- +template +class basic_otp_node::atom_con_hash_fun { + static constexpr size_t s_default_max_ports = 16*1024; + + conn_hash_map& map; + + static size_t init_default_hash_size() { + // See: https://erlang.org/doc/efficiency_guide/advanced.html#ports + const char* p = getenv("EI_MAX_NODE_CONNECTIONS"); + size_t n = 0; + if (p) { + std::stringstream ss(p); + ss >> n; + } + // if (n > 64*1024) n = 64*1024; + return n > 0 ? n : s_default_max_ports; + } +public: + static size_t get_default_hash_size() { + static const size_t s_max_node_connections = init_default_hash_size(); + return s_max_node_connections; + } + + atom_con_hash_fun(conn_hash_map* a_map) : map(*a_map) {} + + size_t operator()(const atom& data) const { + return data.index() % map.bucket_count(); + } +}; + +//----------------------------------------------------------------------------- +// basic_otp_node +//----------------------------------------------------------------------------- +template +basic_otp_node:: +basic_otp_node( + boost::asio::io_service& a_io_svc, + const std::string& a_nodename, const std::string& a_cookie, + const Alloc& a_alloc, int8_t a_creation) + : basic_otp_node_local(a_nodename, a_cookie) + , m_creation((a_creation < 0 ? time(NULL) : (int)a_creation) & 0x03) + , m_pid_count(1) + , m_port_count(1) + , m_refid0(1) + , m_refid1(0) + , m_io_service(a_io_svc) + , m_mailboxes(*this) + , m_connections(atom_con_hash_fun::get_default_hash_size(), atom_con_hash_fun(&m_connections)) + , m_allocator(a_alloc) + , m_verboseness(verboseness::level()) +{} + +template +epid basic_otp_node:: +create_pid() +{ + #ifdef NEW_PID_EXT + uint32_t n = m_pid_count.fetch_add(1, std::memory_order_relaxed); + #else + uint32_t n; + while (true) { + n = uint32_t(m_pid_count.fetch_add(1, std::memory_order_relaxed)); + if (n < 0x0fffffff /* 28 bits */) break; + + if (m_pid_count.exchange(1, std::memory_order_acquire) == 1) { + n = 1; + break; + } + } + #endif + + return epid(m_nodename, n, m_creation, m_allocator); +} + +template +port basic_otp_node:: +create_port() +{ + #ifdef NEW_PID_EXT + uint64_t n = m_port_count.fetch_add(1, std::memory_order_relaxed); + #else + uint64_t n; + while (true) { + n = m_port_count.fetch_add(1, std::memory_order_relaxed); + if (n < 0x0fffffff /* 28 bits */) break; + + if (m_port_count.exchange(1, std::memory_order_acquire) == 1) { + n = 1; + break; + } + } + #endif + + return port(m_nodename, n, m_creation, m_allocator); +} + +template +ref basic_otp_node:: +create_ref() +{ + uint_fast64_t n; + decltype(m_refid1) mo; + + while (true) { + mo = m_refid1.load(std::memory_order_consume); + n = m_refid0.fetch_add(1, std::memory_order_relaxed); + if (n) break; + + auto mn = mo+1; + + #ifndef NEWER_REFERENCE_EXT + if (mn > 0x3ffff) mn = 0; + #endif + + if (m_refid1.compare_exchange_weak(mo, mn, std::memory_order_acquire)) + break; + } + #ifndef NEWER_REFERENCE_EXT + return ref(m_nodename, {mo >> 32, mo & 0xFFFFffff, n >> 32, n & 0xFFFFffff}, + m_creation, m_allocator); + #else + return ref(m_nodename, mo, n, m_creation, m_allocator); + #endif +} + +template +inline basic_otp_mailbox* +basic_otp_node:: +create_mailbox(const atom& a_name, boost::asio::io_service* a_svc) +{ + boost::asio::io_service* p_svc = a_svc ? a_svc : &m_io_service; + return m_mailboxes.create_mailbox(a_name, p_svc); +} + +template +void basic_otp_node:: +close_mailbox(basic_otp_mailbox* a_mbox) +{ + if (a_mbox) + m_mailboxes.erase(a_mbox); +} + + +template +void basic_otp_node:: +set_nodename(const atom& a_nodename, const std::string& a_cookie) +{ + close(); + if (a_nodename != atom()) + basic_otp_node_local::set_nodename(a_nodename.to_string(), a_cookie); +} + +template +void basic_otp_node:: +close() +{ + m_mailboxes.clear(); + for(typename conn_hash_map::iterator + it = m_connections.begin(), end = m_connections.end(); it != end; ++it) + it->second->disconnect(); + m_connections.clear(); +} + +template +template +inline void basic_otp_node:: +connect(CompletionHandler h, const atom& a_remote_nodename, int a_reconnect_secs) +{ + connect(h, a_remote_nodename, atom(), a_reconnect_secs); +} + +template +inline typename basic_otp_node::connection_t& +basic_otp_node:: +connection(atom a_nodename) const +{ + auto l_con = m_connections.find(a_nodename); + if (l_con == m_connections.end()) + throw err_connection("Not connected to node", a_nodename); + return *l_con->second.get(); +} + +template +template +void basic_otp_node:: +connect(CompletionHandler h, const atom& a_remote_node, const atom& a_cookie, + int a_reconnect_secs) +{ + lock_guard guard(m_lock); + typename conn_hash_map::iterator it = m_connections.find(a_remote_node); + if (it == m_connections.end()) { + atom l_cookie = a_cookie.empty() ? cookie() : a_cookie; + typename connection_t::pointer con( + connection_t::connect(h, m_io_service, this, a_remote_node, + l_cookie, a_reconnect_secs)); + m_connections[a_remote_node] = con; + } else { + std::string e; + m_io_service.post(std::bind(h, &*it->second, e)); + } +} + +template +void basic_otp_node:: +on_disconnect_internal(const connection_t& a_con, + atom a_remote_nodename, const boost::system::error_code& err) +{ + if (on_disconnect) + on_disconnect(*this, a_con, a_remote_nodename, err); +} + +template +void basic_otp_node:: +rpc_call(const epid& a_from, const ref& a_ref, + const atom& a_mod, const atom& a_fun, const list& a_args, + const eterm& a_gleader) +{ + auto res = on_rpc_call + ? on_rpc_call(a_from, a_ref, a_mod, a_fun, a_args, a_gleader) + : eterm( + tuple::make(a_ref, + tuple::make(am_error, am_unsupported))); + send(a_from, res); +} + +template +void basic_otp_node:: +publish_port() +{ + throw err_connection("Not implemented!"); +} + +template +void basic_otp_node:: +unpublish_port() +{ + throw std::runtime_error("Not implemented"); +} + +template +void basic_otp_node::start_server() +{ + throw std::runtime_error("Not implemented"); +} + +template +void basic_otp_node::stop_server() +{ + throw std::runtime_error("Not implemented"); +} + +template +void basic_otp_node:: +report_status(report_level a_level, const connection_t* a_con, const std::string& s) +{ + static const char* s_levels[] = {"INFO", "WARN", "ERROR"}; + if (on_status) + on_status(*this, a_con, a_level, s); + else + std::cerr << s_levels[a_level] << "| " << s << std::endl; +} + +template +void basic_otp_node:: +deliver(const transport_msg& a_msg) +{ + try { + const eterm& l_to = a_msg.recipient(); + basic_otp_mailbox* l_mbox = get_mailbox(l_to); + l_mbox->deliver(a_msg); + } catch (std::exception& e) { + // FIXME: Add proper error reporting. + std::stringstream s; + s << "Cannot deliver message " << a_msg.to_string() << ": " << e.what(); + report_status(REPORT_WARNING, NULL, s.str()); + } +} + +template +template +void basic_otp_node:: +send(const atom& a_to_node, ToProc a_to, const transport_msg& a_msg) +{ + if (a_to_node == nodename()) { + basic_otp_mailbox* mbox = m_mailboxes.get(a_to); + if (!mbox) + throw err_no_process(eterm::cast(a_to).to_string()); + mbox->deliver(a_msg); + } else { + connection_t& l_con = connection(a_to_node); + l_con.send(a_msg); + } +} + +template +void inline basic_otp_node:: +send(const epid& a_to, const eterm& a_msg) +{ + transport_msg tm; + tm.set_send(a_to, a_msg, m_allocator); + send(a_to.node(), a_to, tm); +} + +template +void inline basic_otp_node:: +send(const atom& a_node, const epid& a_to, const eterm& a_msg) +{ + transport_msg tm; + tm.set_send(a_to, a_msg, m_allocator); + send(a_node, a_to, tm); +} + +template +void inline basic_otp_node:: +send(const epid& a_from, const atom& a_to, const eterm& a_msg) +{ + transport_msg tm; + tm.set_reg_send(a_from, a_to, a_msg, m_allocator); + send(nodename(), a_to, tm); +} + +template +void inline basic_otp_node:: +send(const epid& a_from, const atom& a_to_node, const atom& a_to, const eterm& a_msg) +{ + transport_msg tm; + tm.set_reg_send(a_from, a_to, a_msg, m_allocator); + send(a_to_node, a_to, tm); +} + +template +void inline basic_otp_node:: +send_rpc(const epid& a_from, + const atom& a_node, const atom& a_mod, const atom& a_fun, + const list& args, const epid* gleader) +{ + static const atom rex("rex"); + transport_msg tm; + tm.set_send_rpc(a_from, a_mod, a_fun, args, gleader, m_allocator); + send(a_node, rex, tm); +} + +template +void inline basic_otp_node:: +send_rpc_cast(const epid& a_from, + const atom& a_node, const atom& a_mod, const atom& a_fun, + const list& args, const epid* gleader) +{ + static const atom rex("rex"); + transport_msg tm; + tm.set_send_rpc_cast(a_from, a_mod, a_fun, args, gleader, m_allocator); + send(a_node, rex, tm); +} + +template +void inline basic_otp_node:: +send_exit(const epid& a_from, const epid& a_to, + const eterm& a_reason) +{ + transport_msg tm; + tm.set_exit(a_from, a_to, a_reason, m_allocator); + send(a_to.node(), a_to, tm); +} + +template +void inline basic_otp_node:: +send_exit2(const epid& a_from, const epid& a_to, + const eterm& a_reason) +{ + transport_msg tm; + tm.set_exit2(a_from, a_to, a_reason, m_allocator); + send(a_to.node(), a_to, tm); +} + +template +void basic_otp_node:: +send_link(const epid& a_from, const epid& a_to) +{ + transport_msg tm; + tm.set_link(a_from, a_to, m_allocator); + send(a_to.node(), a_to, tm); +} + +template +void basic_otp_node:: +send_unlink(const epid& a_from, const epid& a_to) +{ + transport_msg tm; + tm.set_unlink(a_from, a_to, m_allocator); + send(a_to.node(), a_to, tm); +} + +template +const ref& basic_otp_node:: +send_monitor(const epid& a_from, const epid& a_to) +{ + transport_msg tm; + ref r = create_ref(); + tm.set_monitor(a_from, a_to, r, m_allocator); + send(a_to.node(), a_to, tm); + return r; +} + +template +void basic_otp_node:: +send_demonitor(const epid& a_from, const epid& a_to, + const ref& a_ref) +{ + transport_msg tm; + tm.set_demonitor(a_from, a_to, a_ref, m_allocator); + send(a_to.node(), a_to, tm); +} + +template +void basic_otp_node:: +send_monitor_exit(const epid& a_from, const epid& a_to, + const ref& a_ref, const eterm& a_reason) +{ + transport_msg tm; + tm.set_monitor_exit(a_from, a_to, a_ref, a_reason, m_allocator); + send(a_to.node(), a_to, tm); +} + +} // namespace connect +} // namespace eixx diff --git a/include/eixx/connect/basic_otp_node.ipp b/include/eixx/connect/basic_otp_node.ipp deleted file mode 100644 index 2b5fa6a..0000000 --- a/include/eixx/connect/basic_otp_node.ipp +++ /dev/null @@ -1,334 +0,0 @@ -//---------------------------------------------------------------------------- -/// \file basic_otp_mailbox.ipp -//---------------------------------------------------------------------------- -/// \brief Implementation of mailbox interface functions. -//---------------------------------------------------------------------------- -// Copyright (c) 2010 Serge Aleynikov -// Created: 2010-09-20 -//---------------------------------------------------------------------------- -/* -***** BEGIN LICENSE BLOCK ***** - -This file is part of the eixx (Erlang C++ Interface) library. - -Copyright (c) 2010 Serge Aleynikov - -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -***** END LICENSE BLOCK ***** -*/ - -#include -#include - -namespace EIXX_NAMESPACE { -namespace connect { - -////////////////////////////////////////////////////////////////////////// -// enode_local -template -basic_otp_node::basic_otp_node( - boost::asio::io_service& a_io_svc, - const atom& a_nodename, const std::string& a_cookie, - const Alloc& a_alloc, int8_t a_creation) - throw (err_bad_argument, err_connection, eterm_exception) - : basic_otp_node_local(a_nodename.to_string(), a_cookie) - , m_creation((a_creation < 0 ? time(NULL) : (int)a_creation) & 0x03) // Creation counter - , m_pid_count(1) - , m_port_count(1) - , m_serial(0) - , m_io_service(a_io_svc) - , m_mailboxes(*this) - , m_connections(atom_con_hash_fun::get_default_hash_size(), atom_con_hash_fun(&m_connections)) - , m_allocator(a_alloc) -{ - // Init the counters - m_refid[0] = 1; - m_refid[1] = 0; - m_refid[2] = 0; - m_verboseness = verboseness::level(); -} - -template -epid basic_otp_node::create_pid() { - lock_guard guard(m_inc_lock); - epid p(m_nodename, m_pid_count++, m_serial, m_creation, m_allocator); - if (m_pid_count > 0x7fff) { - m_pid_count = 0; - m_serial = (m_serial + 1) & 0x1fff; /* 13 bits */ - } - return p; -} - -template -port basic_otp_node::create_port() { - lock_guard guard(m_inc_lock); - port p(m_nodename, m_port_count++, m_creation, m_allocator); - - if (m_port_count > 0x0fffffff) /* 28 bits */ - m_port_count = 0; - - return p; -} - -template -ref basic_otp_node::create_ref() { - lock_guard guard(m_inc_lock); - ref r(m_nodename, m_refid, m_creation, m_allocator); - - // increment ref ids (3 ints: 18 + 32 + 32 bits) - if (++m_refid[0] > 0x3ffff) { - m_refid[0] = 0; - - if (++m_refid[1] == 0) - ++m_refid[2]; - } - - return r; -} - -template -template -void basic_otp_node::connect( - CompletionHandler h, const atom& a_remote_node, const std::string& a_cookie, - size_t a_reconnect_secs) - throw(err_connection) -{ - lock_guard guard(m_lock); - typename conn_hash_map::iterator it = m_connections.find(a_remote_node); - if (it == m_connections.end()) { - const std::string& l_cookie = a_cookie.empty() ? cookie() : a_cookie; - typename connection_t::pointer con( - connection_t::connect(h, m_io_service, this, a_remote_node, - l_cookie, a_reconnect_secs)); - m_connections[a_remote_node] = con; - } else { - std::string e; - m_io_service.post(boost::bind(h, &*it->second, e)); - } -} - -template -void basic_otp_node::publish_port() throw (err_connection) -{ - throw err_connection("Not implemented!"); -} - -template -void basic_otp_node::unpublish_port() throw (err_connection) -{ - throw std::runtime_error("Not implemented"); -} - -template -void basic_otp_node::start_server() throw(err_connection) -{ - throw std::runtime_error("Not implemented"); -} - -template -void basic_otp_node::stop_server() -{ - throw std::runtime_error("Not implemented"); -} - -template -void basic_otp_node:: -report_status(report_level a_level, const connection_t* a_con, const std::string& s) -{ - static const char* s_levels[] = {"INFO", "WARN", "ERROR"}; - if (on_status) - on_status(*this, a_con, a_level, s); - else - std::cerr << s_levels[a_level] << "| " << s << std::endl; -} - -template -void basic_otp_node::deliver(const transport_msg& a_msg) - throw (err_bad_argument, err_no_process, err_connection) -{ - try { - const eterm& l_to = a_msg.to(); - basic_otp_mailbox* l_mbox = get_mailbox(l_to); - l_mbox->deliver(a_msg); - } catch (std::exception& e) { - // FIXME: Add proper error reporting. - std::stringstream s; - s << "Cannot deliver message " << a_msg.to_string() << ": " << e.what(); - report_status(REPORT_WARNING, NULL, s.str()); - } -} - -template -template -void basic_otp_node::send( - const atom& a_to_node, ToProc a_to, const transport_msg& a_msg) - throw (err_no_process, err_connection) -{ - if (a_to_node == nodename()) { - basic_otp_mailbox* mbox = m_mailboxes.get(a_to); - if (!mbox) - throw err_no_process(eterm::cast(a_to).to_string()); - mbox->deliver(a_msg); - } else { - connection_t& l_con = connection(a_to_node); - l_con.send(a_msg); - } -} - -template -void inline basic_otp_node::send( - const epid& a_to, const eterm& a_msg) - throw (err_no_process, err_connection) -{ - transport_msg tm; - tm.set_send(a_to, a_msg, m_allocator); - send(a_to.node(), a_to, tm); -} - -template -void inline basic_otp_node::send( - const atom& a_node, const epid& a_to, const eterm& a_msg) - throw (err_no_process, err_connection) -{ - transport_msg tm; - tm.set_send(a_to, a_msg, m_allocator); - send(a_node, a_to, tm); -} - -template -void inline basic_otp_node::send( - const epid& a_from, const atom& a_to, const eterm& a_msg) - throw (err_no_process, err_connection) -{ - transport_msg tm; - tm.set_reg_send(a_from, a_to, a_msg, m_allocator); - send(nodename(), a_to, tm); -} - -template -void inline basic_otp_node::send( - const epid& a_from, const atom& a_to_node, const atom& a_to, const eterm& a_msg) - throw (err_no_process, err_connection) -{ - transport_msg tm; - tm.set_reg_send(a_from, a_to, a_msg, m_allocator); - send(a_to_node, a_to, tm); -} - -template -void inline basic_otp_node:: -send_rpc(const epid& a_from, - const atom& a_node, const atom& a_mod, const atom& a_fun, - const list& args, const epid* gleader) - throw (err_bad_argument, err_no_process, err_connection) -{ - static const atom rex("rex"); - transport_msg tm; - tm.set_send_rpc(a_from, a_mod, a_fun, args, gleader, m_allocator); - send(a_node, rex, tm); -} - -template -void inline basic_otp_node:: -send_rpc_cast(const epid& a_from, - const atom& a_node, const atom& a_mod, const atom& a_fun, - const list& args, const epid* gleader) - throw (err_bad_argument, err_no_process, err_connection) -{ - static const atom rex("rex"); - transport_msg tm; - tm.set_send_rpc_cast(a_from, a_mod, a_fun, args, gleader, m_allocator); - send(a_node, rex, tm); -} - -template -void inline basic_otp_node:: -send_exit(const epid& a_from, const epid& a_to, - const eterm& a_reason) - throw (err_no_process, err_connection) -{ - transport_msg tm; - tm.set_exit(a_from, a_to, a_reason, m_allocator); - send(a_to.node(), a_to, tm); -} - -template -void inline basic_otp_node:: -send_exit2(const epid& a_from, const epid& a_to, - const eterm& a_reason) - throw (err_no_process, err_connection) -{ - transport_msg tm; - tm.set_exit2(a_from, a_to, a_reason, m_allocator); - send(a_to.node(), a_to, tm); -} - -template -void basic_otp_node:: -send_link(const epid& a_from, const epid& a_to) - throw (err_no_process, err_connection) -{ - transport_msg tm; - tm.set_link(a_from, a_to, m_allocator); - send(a_to.node(), a_to, tm); -} - -template -void basic_otp_node:: -send_unlink(const epid& a_from, const epid& a_to) - throw (err_no_process, err_connection) -{ - transport_msg tm; - tm.set_unlink(a_from, a_to, m_allocator); - send(a_to.node(), a_to, tm); -} - -template -const ref& basic_otp_node:: -send_monitor(const epid& a_from, const epid& a_to) - throw (err_no_process, err_connection) -{ - transport_msg tm; - ref r = create_ref(); - tm.set_monitor(a_from, a_to, r, m_allocator); - send(a_to.node(), a_to, tm); - return r; -} - -template -void basic_otp_node:: -send_demonitor(const epid& a_from, const epid& a_to, - const ref& a_ref) - throw (err_no_process, err_connection) -{ - transport_msg tm; - tm.set_demonitor(a_from, a_to, a_ref, m_allocator); - send(a_to.node(), a_to, tm); -} - -template -void basic_otp_node:: -send_monitor_exit(const epid& a_from, const epid& a_to, - const ref& a_ref, const eterm& a_reason) - throw (err_no_process, err_connection) -{ - transport_msg tm; - tm.set_monitor_exit(a_from, a_to, a_ref, a_reason, m_allocator); - send(a_to.node(), a_to, tm); -} - -} // namespace connect -} // namespace EIXX_NAMESPACE diff --git a/include/eixx/connect/basic_otp_node_local.hpp b/include/eixx/connect/basic_otp_node_local.hpp index a11e15c..66e84bc 100644 --- a/include/eixx/connect/basic_otp_node_local.hpp +++ b/include/eixx/connect/basic_otp_node_local.hpp @@ -12,23 +12,19 @@ /* ***** BEGIN LICENSE BLOCK ***** -This file is part of the eixx (Erlang C++ Interface) library. +Copyright 2010 Serge Aleynikov -Copyright (c) 2010 Serge Aleynikov +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. + http://www.apache.org/licenses/LICENSE-2.0 -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. ***** END LICENSE BLOCK ***** */ @@ -40,7 +36,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include #include -namespace EIXX_NAMESPACE { +namespace eixx { namespace connect { using marshal::atom; @@ -52,29 +48,32 @@ using marshal::atom; class basic_otp_node_local { public: basic_otp_node_local() {} - basic_otp_node_local(const std::string& a_nodename, const std::string& a_cookie = "") - throw (std::runtime_error, err_bad_argument); + + /// @throws std::runtime_error + /// @throws err_bad_argument + basic_otp_node_local(const std::string& a_nodename, const std::string& a_cookie = ""); virtual ~basic_otp_node_local() {} /// Change the nodename of current node. - void set_nodename(const std::string& a_nodename, const std::string& a_cookie = "") - throw (std::runtime_error, err_bad_argument); + /// @throws std::runtime_error + /// @throws err_bad_argument + void set_nodename(const std::string& a_nodename, const std::string& a_cookie = ""); /// Get node name in the form node@host. - const atom& nodename() const { return m_nodename; } + atom nodename() const { return m_nodename; } /// Get node name in the form node@host.name.com. - const std::string& longname() const { return m_longname; } + const std::string& longname() const { return m_longname; } /// Get name of the node without hostname - const std::string& alivename() const { return m_alivename; } + const std::string& alivename() const { return m_alivename; } /// Get host name - const std::string& hostname() const { return m_hostname; } + const std::string& hostname() const { return m_hostname; } /// Get cookie - const std::string& cookie() const { return m_cookie; } + atom cookie() const { return m_cookie; } /// Set the cookie void cookie(const std::string& a_cookie) { m_cookie = a_cookie; } @@ -87,13 +86,13 @@ class basic_otp_node_local { std::string m_longname; std::string m_alivename; std::string m_hostname; - std::string m_cookie; + atom m_cookie; - static std::string s_default_cookie; // Default cookie + static atom s_default_cookie; // Default cookie static std::string s_localhost; // localhost name }; } // namespace connect -} // namespace EIXX_NAMESPACE +} // namespace eixx #endif // _EIXX_OTP_NODE_LOCAL_HPP_ diff --git a/include/eixx/connect/detail/basic_rpc_server.hpp b/include/eixx/connect/detail/basic_rpc_server.hpp new file mode 100644 index 0000000..b67a574 --- /dev/null +++ b/include/eixx/connect/detail/basic_rpc_server.hpp @@ -0,0 +1,179 @@ +//---------------------------------------------------------------------------- +/// \file basic_rpc_server.hpp +//---------------------------------------------------------------------------- +/// \brief A class implementing RPC protocol support for an Erlang node +//---------------------------------------------------------------------------- +// Copyright (c) 2013 Serge Aleynikov +// Created: 2013-10-21 +//---------------------------------------------------------------------------- +/* +***** BEGIN LICENSE BLOCK ***** + +Copyright 2010 Serge Aleynikov + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +***** END LICENSE BLOCK ***** +*/ + +#ifndef _EIXX_BASIC_RPC_SERVER_HPP_ +#define _EIXX_BASIC_RPC_SERVER_HPP_ + +#include +#include +#include +#include + +namespace eixx { +namespace connect { + +using marshal::atom; + +/** + * Implements Erlang RPC protocol + */ +template +class basic_otp_node::rpc_server : private Alloc +{ + static const var A; + static const var F; + static const var G; + static const var M; + static const var P; + static const var R; + static const var T; + + basic_otp_node& m_node; + bool m_active; + std::unique_ptr> m_self; + + void rpc_call(const epid& a_from, const ref& a_ref, + const atom& a_mod, const atom& a_fun, const list& a_list, + const eterm& a_gleader) + { + if (m_node.on_rpc_call) + m_node.on_rpc_call(a_from, a_ref, a_mod, a_fun, a_list, a_gleader); + } + +public: + rpc_server(basic_otp_node& a_owner, const Alloc& a_alloc = Alloc()) + : Alloc(a_alloc) + , m_node(a_owner) + , m_active(true) + , m_self(a_owner.create_mailbox(am_rex)) + {} + + ~rpc_server() { + close(); + } + + void close() { + m_active = false; + m_self.reset(); + } + + /// Encode a tuple containing RPC call details + static tuple encode_rpc(const epid& a_from, + const atom& a_mod, const atom& a_fun, + const list& a_args, + const eterm& a_gleader) + { + // {Self, {call, Mod, Fun, Args, GroupLeader}} + return tuple::make(a_from, + tuple::make(am_call, a_mod, a_fun, a_args, a_gleader)); + } + + /// Encode a tuple containing RPC cast details + static tuple encode_rpc_cast(const epid& /*a_from*/, + const atom& a_mod, const atom& a_fun, + const list& a_args, + const eterm& a_gleader) + { + // {'$gen_cast', { cast, Mod, Fun, Args, GroupLeader}} + return tuple::make(am_gen_cast, + tuple::make(am_cast, a_mod, a_fun, a_args, a_gleader)); + } + + static eterm decode_rpc(const eterm& a_msg) { + static const eterm s_pattern = eterm::format("{rex, ~v}", var(T)); + + if (a_msg.type() != TUPLE) + return eterm(); + varbind binding; + return a_msg.match(s_pattern, &binding) ? binding[T] : eterm(); + } + + bool operator() (const eterm& /*a_pat*/, + const varbind& /*a_binding*/, + long /*a_opaque*/) + { + // TODO: What do we do on the incoming RPC call? Need to define + // afacility to add RPC callable functions + return false; + } + + void start() { + static const marshal::eterm_pattern_matcher& s_matcher = + { + marshal::eterm_pattern_action( + this->get_allocator(), + [this](auto& pat, auto& binding, auto opaque) { + this->m_node.rpc_call(pat, binding, opaque); + }, + 0, + "{'$gen_call', {~w, ~w}, {call, ~w, ~w, ~w, ~w}}", + P, R, M, F, A, G), + + marshal::eterm_pattern_action( + this->get_allocator(), + [this](auto& pat, auto& binding, auto opaque) { + this->m_node.rpc_call(pat, binding, opaque); + }, + 1, + "{'$gen_cast', {cast, ~w, ~w, ~w, ~w}}", + M, F, A, G), + + marshal::eterm_pattern_action( + this->get_allocator(), + [this](auto& pat, auto& binding, auto opaque) { + this->m_node.rpc_call(pat, binding, opaque); + }, + 2, + "{_, {~w, ~w}, _Cmd}}", + P, R) + }; + + m_self->async_match(s_matcher, *this, std::chrono::milliseconds(-1), -1); + } +}; + +template +const var basic_otp_node::rpc_server::A = var("A", LIST); +template +const var basic_otp_node::rpc_server::F = var("F", ATOM); +template +const var basic_otp_node::rpc_server::G = var("G"); +template +const var basic_otp_node::rpc_server::M = var("M", ATOM); +template +const var basic_otp_node::rpc_server::P = var("P", PID); +template +const var basic_otp_node::rpc_server::R = var("R", REF); +template +const var basic_otp_node::rpc_server::T = var("T"); + + +} // namespace connect +} // namespace eixx + +#endif // _EIXX_BASIC_RPC_SERVER_HPP_ diff --git a/include/eixx/connect/test_helper.hpp b/include/eixx/connect/test_helper.hpp index aa81792..65e10a5 100644 --- a/include/eixx/connect/test_helper.hpp +++ b/include/eixx/connect/test_helper.hpp @@ -12,7 +12,7 @@ #include -namespace EIXX_NAMESPACE { +namespace eixx { namespace connect { class verboseness { @@ -44,7 +44,7 @@ class verboseness { typedef connect::verboseness verboseness; -} // namespace EIXX_NAMESPACE +} // namespace eixx #endif // _EIXX_TEST_HELPER_HPP_ diff --git a/include/eixx/connect/transport_msg.hpp b/include/eixx/connect/transport_msg.hpp index e419b3c..0c47598 100644 --- a/include/eixx/connect/transport_msg.hpp +++ b/include/eixx/connect/transport_msg.hpp @@ -9,23 +9,19 @@ /* ***** BEGIN LICENSE BLOCK ***** -This file is part of the eixx (Erlang C++ Interface) library. +Copyright 2010 Serge Aleynikov -Copyright (c) 2010 Serge Aleynikov +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. + http://www.apache.org/licenses/LICENSE-2.0 -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. ***** END LICENSE BLOCK ***** */ @@ -37,15 +33,15 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include #include -namespace EIXX_NAMESPACE { +namespace eixx { namespace connect { -using EIXX_NAMESPACE::marshal::tuple; -using EIXX_NAMESPACE::marshal::list; -using EIXX_NAMESPACE::marshal::eterm; -using EIXX_NAMESPACE::marshal::epid; -using EIXX_NAMESPACE::marshal::ref; -using EIXX_NAMESPACE::marshal::trace; +using eixx::marshal::tuple; +using eixx::marshal::list; +using eixx::marshal::eterm; +using eixx::marshal::epid; +using eixx::marshal::ref; +using eixx::marshal::trace; /// Erlang distributed transport messages contain message type, /// control message with message routing and other details, and @@ -76,6 +72,14 @@ class transport_msg { , NO_EXCEPTION_MASK = (uint32_t)EXCEPTION-1 }; +private: + // Note that the m_type is mutable so that we can call set_error_flag() on + // constant objects. + mutable transport_msg_type m_type; + tuple m_cntrl; + eterm m_msg; + +public: transport_msg() : m_type(UNDEFINED) {} transport_msg(int a_msgtype, const tuple& a_cntrl, const eterm* a_msg = NULL) @@ -89,17 +93,23 @@ class transport_msg { : m_type(rhs.m_type), m_cntrl(rhs.m_cntrl), m_msg(rhs.m_msg) {} + transport_msg(transport_msg&& rhs) + : m_type(rhs.m_type), m_cntrl(std::move(rhs.m_cntrl)), m_msg(std::move(rhs.m_msg)) + { + rhs.m_type = UNDEFINED; + } + /// Return a string representation of the transport message type. const char* type_string() const; /// Transport message type transport_msg_type type() const { return m_type; } - int to_type() const { return bit_scan_forward(m_type); } + int to_type() const { return m_type == UNDEFINED ? 0 : bit_scan_forward(m_type); } const tuple& cntrl() const { return m_cntrl;} const eterm& msg() const { return m_msg; } /// Returns true when the transport message contains message payload /// associated with SEND or REG_SEND message type. - bool has_msg() const { return m_msg.type() != EIXX_NAMESPACE::UNDEFINED; } + bool has_msg() const { return m_msg.type() != eixx::UNDEFINED; } /// Indicates that there was an error processing this message bool has_error() const { return (m_type & EXCEPTION) == EXCEPTION; } @@ -110,9 +120,9 @@ class transport_msg { } /// Return the term representing the message sender. The sender is - /// usually a pid, except for MONITOR_P_EXIT message type for which + /// usually a pid, except for MONITOR_P_EXIT message type for which /// the sender can be either pid or atom name. - const eterm& from() const { + const eterm& sender() const { switch (m_type) { case REG_SEND: case LINK: @@ -134,14 +144,15 @@ class transport_msg { /// This function may only raise exception for MONITOR_P_EXIT /// message types if the message sender is given by name rather than by pid. - const epid& from_pid() const throw (err_wrong_type) { - return from().to_pid(); + /// @throws err_wrong_type + const epid& sender_pid() const { + return sender().to_pid(); } /// Return the term representing the message sender. The sender is /// usually a pid, except for MONITOR_P|DEMONITOR_P message type for which /// the sender can be either pid or atom name. - const eterm& to() const { + const eterm& recipient() const { switch (m_type) { case REG_SEND: return m_cntrl[3]; @@ -167,15 +178,18 @@ class transport_msg { /// This function may only raise exception for MONITOR_P|DEMONITOR_P /// message types if the message sender is given by name rather than by pid. - const epid& to_pid() const throw (err_wrong_type) { - return to().to_pid(); + /// @throws err_wrong_type + const epid& recipient_pid() const { + return recipient().to_pid(); } - const atom& to_name() const throw (err_wrong_type) { - return to().to_atom(); + /// @throws err_wrong_type + const atom& recipient_name() const { + return recipient().to_atom(); } - const eterm& trace_token() const throw (err_wrong_type) { + /// @throws err_wrong_type + const eterm& trace_token() const { switch (m_type) { case SEND_TT: case EXIT_TT: @@ -186,7 +200,8 @@ class transport_msg { } } - const ref& get_ref() const throw (err_wrong_type) { + /// @throws err_wrong_type + const ref& get_ref() const { switch (m_type) { case MONITOR_P: case DEMONITOR_P: @@ -197,7 +212,8 @@ class transport_msg { } } - const eterm& reason() const throw (err_wrong_type) { + /// @throws err_wrong_type + const eterm& reason() const { switch (m_type) { case EXIT: case EXIT2: return m_cntrl[3]; @@ -221,8 +237,8 @@ class transport_msg { /// Set the current message to represent a SEND message containing \a a_msg to /// be sent to \a a_to pid. - void set_send(const epid& a_to, const eterm& a_msg, - const Alloc& a_alloc = Alloc()) + void set_send(const epid& a_to, const eterm& a_msg, + const Alloc& a_alloc = Alloc()) { const trace* token = trace::tracer(marshal::TRACE_GET); if (unlikely(token)) { @@ -238,8 +254,8 @@ class transport_msg { /// Set the current message to represent a REG_SEND message containing /// \a a_msg to be sent from \a a_from pid to \a a_to registered mailbox. - void set_reg_send(const epid& a_from, const atom& a_to, const eterm& a_msg, - const Alloc& a_alloc = Alloc()) + void set_reg_send(const epid& a_from, const atom& a_to, + const eterm& a_msg, const Alloc& a_alloc = Alloc()) { const trace* token = trace::tracer(marshal::TRACE_GET); if (unlikely(token)) { @@ -255,9 +271,10 @@ class transport_msg { /// Set the current message to represent a LINK message. void set_link(const epid& a_from, const epid& a_to, - const Alloc& a_alloc = Alloc()) + const Alloc& a_alloc = Alloc()) { - const tuple& l_cntrl = tuple::make(ERL_LINK, a_from, a_to, a_alloc); + const tuple& l_cntrl = + tuple::make(ERL_LINK, a_from, a_to, a_alloc); set(LINK, l_cntrl, NULL); } @@ -265,7 +282,8 @@ class transport_msg { void set_unlink(const epid& a_from, const epid& a_to, const Alloc& a_alloc = Alloc()) { - const tuple& l_cntrl = tuple::make(ERL_UNLINK, a_from, a_to, a_alloc); + const tuple& l_cntrl = + tuple::make(ERL_UNLINK, a_from, a_to, a_alloc); set(UNLINK, l_cntrl, NULL); } @@ -313,7 +331,7 @@ class transport_msg { } /// Set the current message to represent a MONITOR_EXIT message. - void set_monitor_exit(const epid& a_from, const epid& a_to, + void set_monitor_exit(const epid& a_from, const epid& a_to, const ref& a_ref, const eterm& a_reason, const Alloc& a_alloc = Alloc()) { @@ -384,12 +402,6 @@ class transport_msg { } private: - // Note that the m_type is mutable so that we can call set_error_flag() on - // constant objects. - mutable transport_msg_type m_type; - tuple m_cntrl; - eterm m_msg; - void set_exit_internal(int a_type, int a_trace_type, const epid& a_from, const epid& a_to, const eterm& a_reason, const Alloc& a_alloc = Alloc()) @@ -444,18 +456,19 @@ const char* transport_msg::type_string() const { case DEMONITOR_P: return "DEMONITOR_P"; case MONITOR_P_EXIT: return "MONITOR_P_EXIT"; default: { - std::stringstream str; str << "UNSUPPORTED(" << bit_scan_forward(m_type) << ')'; - return str.str().c_str(); + // std::stringstream str; str << "UNSUPPORTED(" << bit_scan_forward(m_type) << ')'; + // return str.str().c_str(); + return "UNSUPPORTED"; } } } } // namespace connect -} // namespace EIXX_NAMESPACE +} // namespace eixx namespace std { template - ostream& operator<< (ostream& out, EIXX_NAMESPACE::connect::transport_msg& a_msg) { + ostream& operator<< (ostream& out, eixx::connect::transport_msg& a_msg) { return a_msg.dump(out); } } // namespace std diff --git a/include/eixx/connect/transport_otp_connection.hpp b/include/eixx/connect/transport_otp_connection.hpp index 4761177..c3a7999 100644 --- a/include/eixx/connect/transport_otp_connection.hpp +++ b/include/eixx/connect/transport_otp_connection.hpp @@ -8,43 +8,88 @@ //---------------------------------------------------------------------------- /* * ***** BEGIN LICENSE BLOCK ***** - * - * This file is part of the eixx (Erlang C++ Interface) library. - * - * Copyright (c) 2010 Serge Aleynikov - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * + +Copyright 2010 Serge Aleynikov + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + * ***** END LICENSE BLOCK ***** */ #ifndef _EIXX_TRANSPORT_OTP_CONNECTION_HPP_ #define _EIXX_TRANSPORT_OTP_CONNECTION_HPP_ +#include #include #include #include #include -#include #include #include #include #include #include -namespace EIXX_NAMESPACE { +#ifdef HAVE_EI_EPMD +extern "C" { + +#include // see erl_interface/src +#include // ERL_VERSION_MAGIC +#include // see erl_interface/src + +} +#else +// These constants are not exposed by EI headers: +// See: https://github.com/erlang/otp/blob/OTP-24.0.5/lib/erl_interface/src/connect/ei_connect_int.h +#define ERL_VERSION_MAGIC 131 +#define EPMD_PORT 4369 +#define EPMDBUF 512 +#define EI_EPMD_PORT2_REQ 122 +#define EI_EPMD_PORT2_RESP 119 +#define EI_DIST_5 5 /* OTP R4 - 22 */ +#define EI_DIST_6 6 /* OTP 23 and later */ +#define EI_DIST_LOW EI_DIST_5 +#define EI_DIST_HIGH EI_DIST_6 +#define DFLAG_PUBLISHED 1 +#define DFLAG_ATOM_CACHE 2 +#define DFLAG_EXTENDED_REFERENCES 4 +#define DFLAG_DIST_MONITOR 8 +#define DFLAG_FUN_TAGS 0x10 +#define DFLAG_NEW_FUN_TAGS 0x80 +#define DFLAG_EXTENDED_PIDS_PORTS 0x100 +#define DFLAG_EXPORT_PTR_TAG 0x200 +#define DFLAG_BIT_BINARIES 0x400 +#define DFLAG_NEW_FLOATS 0x800 +#define DFLAG_SMALL_ATOM_TAGS 0x4000 +#define DFLAG_UTF8_ATOMS 0x10000 +#define DFLAG_MAP_TAG 0x20000 +#define DFLAG_BIG_CREATION 0x40000 +#define DFLAG_HANDSHAKE_23 0x1000000 +#define DFLAG_UNLINK_ID 0x2000000 + +/* + * As the old handshake only support 32 flag bits, we reserve the remaining + * bits in the lower 32 for changes in the handshake protocol or potentially + * new capabilities that we also want to backport to OTP-22 or older. + */ +typedef EI_ULONGLONG DistFlags; +#define DFLAG_RESERVED 0xfc000000 +#define DFLAG_NAME_ME (((DistFlags)0x2) << 32) +#define DFLAG_V4_NC (((DistFlags)0x4) << 32) + +#endif + +namespace eixx { namespace connect { namespace posix = boost::asio::posix; @@ -55,6 +100,34 @@ enum connection_type { UNDEFINED, TCP, UDS }; /// Convert connection type to string. const char* connection_type_to_str(connection_type a_type); +//------------------------------------------------------------------------------ +// capability flags supported by this class +//------------------------------------------------------------------------------ +// See: https://github.com/erlang/otp/blob/OTP-24.0.5/lib/erl_interface/src/connect/ei_connect.c#L2272-L2287 +inline constexpr uint64_t LOCAL_FLAGS = ( + DFLAG_EXTENDED_REFERENCES + | DFLAG_DIST_MONITOR + | DFLAG_EXTENDED_PIDS_PORTS + | DFLAG_FUN_TAGS + | DFLAG_NEW_FUN_TAGS + | DFLAG_NEW_FLOATS + | DFLAG_SMALL_ATOM_TAGS + | DFLAG_UTF8_ATOMS + | DFLAG_MAP_TAG + | DFLAG_BIG_CREATION + | DFLAG_EXPORT_PTR_TAG + | DFLAG_BIT_BINARIES +#ifdef DFLAG_HANDSHAKE_23 + | DFLAG_HANDSHAKE_23 +#endif +#ifdef DFLAG_V4_NC + | DFLAG_V4_NC +#endif +#ifdef DFLAG_UNLINK_ID + // | DFLAG_UNLINK_ID +#endif + ); + //---------------------------------------------------------------------------- // Base connection class. //---------------------------------------------------------------------------- @@ -76,9 +149,10 @@ class connection /// The handler used to process the incoming request. Handler* m_handler; connection_type m_type; - std::string m_remote_node; - std::string m_this_node; - std::string m_cookie; + atom m_remote_nodename; + atom m_this_node; + uint32_t m_this_creation; + atom m_cookie; Alloc m_allocator; @@ -133,7 +207,7 @@ class connection size_t available_queue() const { return m_available_queue; } char* rd_ptr() { return m_rd_ptr; } - size_t rd_length() { return m_rd_end - m_rd_ptr; } + size_t rd_length() { return static_cast(m_rd_end - m_rd_ptr); } size_t rd_capacity() { return m_rd_buf.capacity() - rd_length(); } /// Verboseness verbose_type verbose() const { return m_handler->verbose(); } @@ -145,24 +219,36 @@ class connection void do_write_internal() { if (!m_is_writing && !m_out_msg_queue[available_queue()].empty()) { +#if BOOST_VERSION >= 106600 + std::deque buffers = m_out_msg_queue[available_queue()]; +#else typedef boost::asio::detail::consuming_buffers< boost::asio::const_buffer, std::deque > cb_t; cb_t buffers(m_out_msg_queue[available_queue()]); +#endif m_is_writing = true; flip_queues(); // Work on the data accumulated in the available_queue. - if (unlikely(verbose() >= VERBOSE_WIRE)) - for(cb_t::const_iterator it=buffers.begin(); it != buffers.end(); ++it) { + if (unlikely(verbose() >= VERBOSE_WIRE)) { +#if BOOST_VERSION >= 106600 + auto begin = boost::asio::buffer_sequence_begin(buffers); + auto end = boost::asio::buffer_sequence_end(buffers); +#else + auto begin = buffers.begin(); + auto end = buffers.end(); +#endif + for(auto it=begin; it != end; ++it) { std::stringstream s; s << " async_write " << boost::asio::buffer_size(*it) << " bytes: " << to_binary_string(boost::asio::buffer_cast(*it), boost::asio::buffer_size(*it)); m_handler->report_status(REPORT_INFO, s.str()); } + } + auto pthis = this->shared_from_this(); async_write(buffers, boost::asio::transfer_all(), - boost::bind(&connection::handle_write, this->shared_from_this(), - boost::asio::placeholders::error)); + [pthis](auto& ec, std::size_t) { pthis->handle_write(ec); }); } } @@ -174,8 +260,8 @@ class connection /// Note: TICK message is represented by msg type = 0, in this case \a a_cntrl_msg /// and \a a_msg are invalid. /// @return Control Message - int transport_msg_decode(const char *mbuf, int len, transport_msg& a_tm) - throw(err_decode_exception); + /// @throws err_decode_exception + int transport_msg_decode(const char *mbuf, size_t len, transport_msg& a_tm); void process_message(const char* a_buf, size_t a_size); @@ -209,29 +295,31 @@ class connection } boost::asio::const_buffer b(data, sz); - m_io_service.post( - boost::bind(&connection::do_write, this->shared_from_this(), b)); + auto pthis = this->shared_from_this(); + m_io_service.post([pthis, b]() { pthis->do_write(b); }); } /// Get connection type from string. If successful the string is /// modified to exclude the "...://" prefix. /// @param is a connection address (e.g. "tcp://node@host"). /// @return connection type derived from s. - static connection_type parse_connection_type(std::string& s) - throw(std::runtime_error); + /// @throws std::runtime_error + static connection_type parse_connection_type(std::string& s); - /// Establish connection to \a a_remote_node. The call is non-blocking - + /// Establish connection to \a a_remote_nodename. The call is non-blocking - /// it will immediately returned, and Handler's on_connect() or /// on_error() callback will be invoked on successful/failed connection /// status. - virtual void connect(const std::string& a_this_node, - const std::string& a_remote_node, - const std::string& a_cookie) - throw(std::runtime_error) + /// @throws std::runtime_error + virtual void connect(uint32_t a_this_creation, + atom a_this_node, + atom a_remote_nodename, + atom a_cookie) { - m_this_node = a_this_node; - m_remote_node = a_remote_node; - m_cookie = a_cookie; + m_this_creation = a_this_creation; + m_this_node = a_this_node; + m_remote_nodename = a_remote_nodename; + m_cookie = a_cookie; } /// Set the socket to non-blocking mode and issue on_connect() callback. @@ -250,12 +338,12 @@ class connection m_handler->on_connect(this); const boost::asio::mutable_buffers_1 buffers(m_rd_end, rd_capacity()); + auto pthis = this->shared_from_this(); async_read( buffers, boost::asio::transfer_at_least(s_header_size), - boost::bind(&connection::handle_read, this->shared_from_this(), - boost::asio::placeholders::error, - boost::asio::placeholders::bytes_transferred)); + [pthis](auto& ec, auto bytes) { pthis->handle_read(ec, bytes); } + ); } template @@ -265,8 +353,8 @@ class connection void async_write(const MutableBuffers& b, const CompletionCondition& c, ReadHandler h); public: - typedef Handler handler_type; - typedef boost::shared_ptr > pointer; + using handler_type = Handler; + using pointer = boost::shared_ptr>; /// Create a connection object given of specific type and connect to peer /// endpoint given by \a a_addr. @@ -282,12 +370,13 @@ class connection /// \endverbatim /// @param a_cookie security cookie. static pointer create( - boost::asio::io_service& a_svc, - handler_type* a_h, - const std::string& a_this_node, - const std::string& a_node, - const std::string& a_cookie, - const Alloc& a_alloc = Alloc()); + boost::asio::io_service& a_svc, + handler_type* a_h, + uint32_t a_this_creation, + atom a_this_node, + atom a_node, + atom a_cookie, + const Alloc& a_alloc = Alloc()); virtual ~connection() { if (handler()->verbose() >= VERBOSE_TRACE) @@ -323,14 +412,16 @@ class connection } virtual int native_socket() = 0; + virtual uint64_t remote_flags() const = 0; /// Address of connected peer. - virtual std::string peer_address() const { return ""; } - const std::string& remote_node() const { return m_remote_node; } - const std::string& this_node() const { return m_this_node; } - const std::string& cookie() const { return m_cookie; } - Handler* handler() { return m_handler; } - boost::asio::io_service& io_service() { return m_io_service; } + virtual std::string peer_address() const { return ""; } + atom remote_nodename() const { return m_remote_nodename; } + atom local_nodename() const { return m_this_node; } + uint32_t local_creation() const { return m_this_creation; } + atom cookie() const { return m_cookie; } + Handler* handler() { return m_handler; } + boost::asio::io_service& io_service() { return m_io_service; } /// Send a message \a a_msg to the remote node. void send(const transport_msg& a_msg); @@ -345,8 +436,8 @@ class connection //------------------------------------------------------------------------------ } // namespace connect -} // namespace EIXX_NAMESPACE +} // namespace eixx -#include +#include #endif // _EIXX_TRANSPORT_OTP_CONNECTION_HPP_ diff --git a/include/eixx/connect/transport_otp_connection.ipp b/include/eixx/connect/transport_otp_connection.hxx similarity index 81% rename from include/eixx/connect/transport_otp_connection.ipp rename to include/eixx/connect/transport_otp_connection.hxx index f5bdf58..69bb4e4 100644 --- a/include/eixx/connect/transport_otp_connection.ipp +++ b/include/eixx/connect/transport_otp_connection.hxx @@ -1,5 +1,5 @@ //---------------------------------------------------------------------------- -/// \file transport_otp_connection.ipp +/// \file transport_otp_connection.hxx //---------------------------------------------------------------------------- // Copyright (c) 2010 Serge Aleynikov // Created: 2010-09-11 @@ -7,23 +7,19 @@ /* ***** BEGIN LICENSE BLOCK ***** -This file is part of the eixx (Erlang C++ Interface) library. +Copyright 2010 Serge Aleynikov -Copyright (c) 2010 Serge Aleynikov +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. + http://www.apache.org/licenses/LICENSE-2.0 -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. ***** END LICENSE BLOCK ***** */ @@ -39,16 +35,16 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include //#include // see erl_interface/src -namespace EIXX_NAMESPACE { +namespace eixx { namespace connect { -using EIXX_NAMESPACE::marshal::trace; +using eixx::marshal::trace; template const size_t connection::s_header_size = 4; template -const char connection::s_header_magic = 132; +const char connection::s_header_magic = static_cast(132); template const eterm connection::s_null_cookie; @@ -59,15 +55,15 @@ typename connection::pointer connection::create( boost::asio::io_service& a_svc, Handler* a_h, - const std::string& a_this_node, - const std::string& a_node, - const std::string& a_cookie, + uint32_t a_this_creation, + atom a_this_node, + atom a_node, + atom a_cookie, const Alloc& a_alloc) { - if (a_this_node.find('@') == std::string::npos) - THROW_RUNTIME_ERROR("Invalid name of this node: " << a_this_node); + BOOST_ASSERT(a_this_node.to_string().find('@') != std::string::npos); - std::string addr(a_node); + std::string addr(a_node.to_string()); connection_type con_type = parse_connection_type(addr); @@ -91,13 +87,13 @@ connection::create( default: THROW_RUNTIME_ERROR("Not implemented! (proto=" << con_type << ')'); } - p->connect(a_this_node, addr, a_cookie); + p->connect(a_this_creation, a_this_node, a_node, a_cookie); return p; } template connection_type -connection::parse_connection_type(std::string& s) throw(std::runtime_error) +connection::parse_connection_type(std::string& s) { size_t pos = s.find("://", 0); if (pos == std::string::npos) @@ -177,7 +173,7 @@ handle_write(const boost::system::error_code& err) return; } - if (unlikely(err)) { + if (unlikely(bool(err))) { if (verbose() >= VERBOSE_TRACE) { std::stringstream s; s << "connection::handle_write(" << err.value() << ')'; @@ -186,16 +182,13 @@ handle_write(const boost::system::error_code& err) // We use operation_aborted as a user-initiated connection reset, // therefore check to substitute the error since bytes_transferred == 0 // means a connection loss. - boost::system::error_code e = - err == boost::asio::error::operation_aborted + boost::system::error_code e = err == boost::asio::error::operation_aborted ? boost::asio::error::not_connected : err; stop(e); return; } - for (std::deque::iterator - it = m_out_msg_queue[writing_queue()].begin(), - end = m_out_msg_queue[writing_queue()].end(); - it != end; ++it) { + auto& q = m_out_msg_queue[writing_queue()]; + for (auto it = q.begin(), end = q.end(); it != end; ++it) { const char* p = boost::asio::buffer_cast(*it); // Don't forget to adjust for the header magic byte. BOOST_ASSERT(*(p - 1) == s_header_magic); @@ -231,7 +224,7 @@ handle_read(const boost::system::error_code& err, size_t bytes_transferred) "Connection aborted - exiting connection::handle_read"); } return; - } else if (unlikely(err)) { + } else if (unlikely(bool(err))) { // We use operation_aborted as a user-initiated connection reset, // therefore check to substitute the error since bytes_transferred == 0 // means a connection loss. @@ -254,7 +247,7 @@ handle_read(const boost::system::error_code& err, size_t bytes_transferred) // next message. m_packet_size = cast_be(m_rd_ptr); if (m_packet_size > m_rd_buf.capacity()-s_header_size) { - size_t begin_offset = m_rd_ptr - &m_rd_buf[0]; + size_t begin_offset = static_cast(m_rd_ptr - &m_rd_buf[0]); m_rd_buf.reserve(begin_offset + m_packet_size + s_header_size); m_rd_ptr = &m_rd_buf[0] + begin_offset; m_rd_end = m_rd_ptr + len; @@ -262,7 +255,7 @@ handle_read(const boost::system::error_code& err, size_t bytes_transferred) } } - long need_bytes = m_packet_size + s_header_size - rd_length(); + auto need_bytes = m_packet_size + s_header_size - rd_length(); /* if (unlikely(verbose() >= VERBOSE_WIRE)) @@ -272,7 +265,7 @@ handle_read(const boost::system::error_code& err, size_t bytes_transferred) << ", length=" << rd_length() << ", rd_buf.size=" << m_rd_buf.capacity() << ", got_header=" << (m_got_header ? "true" : "false") - << ", " << to_binary_string(m_rd_ptr, std::min(rd_length(), 15lu)) << "..." + << ", " << to_binary_string(m_rd_ptr, std::min(rd_length(), 25lu)) << "..." << std::endl; */ @@ -282,7 +275,6 @@ handle_read(const boost::system::error_code& err, size_t bytes_transferred) m_in_msg_count++; try { - /* if (unlikely(verbose() >= VERBOSE_WIRE)) { std::cout << " MsgCnt=" << m_in_msg_count << ", pkt_size=" << m_packet_size << ", need=" << need_bytes @@ -295,7 +287,6 @@ handle_read(const boost::system::error_code& err, size_t bytes_transferred) to_binary_string( std::cout << "client <- server: ", m_rd_ptr, m_packet_size) << std::endl; } - */ // Decode the packet into a message and dispatch it. process_message(m_rd_ptr, m_packet_size); @@ -306,7 +297,7 @@ handle_read(const boost::system::error_code& err, size_t bytes_transferred) to_binary_string(m_rd_ptr, m_packet_size)); } m_rd_ptr += m_packet_size; - m_got_header = rd_length() >= (long)s_header_size; + m_got_header = rd_length() >= long(s_header_size); if (m_got_header) { m_packet_size = cast_be(m_rd_ptr); need_bytes = m_packet_size + s_header_size - rd_length(); @@ -321,9 +312,9 @@ handle_read(const boost::system::error_code& err, size_t bytes_transferred) need_bytes = m_packet_size; } else if ((m_rd_ptr - (&m_rd_buf[0] + s_header_size)) > 0) { // Crunch the buffer by copying leftover bytes to the beginning of the buffer. - const size_t len = m_rd_end - m_rd_ptr; + const size_t len = static_cast(m_rd_end - m_rd_ptr); char* begin = &m_rd_buf[0]; - if (likely((size_t)(m_rd_ptr - begin) < len)) + if (likely(static_cast(m_rd_ptr - begin) < len)) memcpy(begin, m_rd_ptr, len); else memmove(begin, m_rd_ptr, len); @@ -347,9 +338,9 @@ handle_read(const boost::system::error_code& err, size_t bytes_transferred) boost::asio::mutable_buffers_1 buffers(m_rd_end, rd_capacity()); async_read( buffers, boost::asio::transfer_at_least(need_bytes), - boost::bind(&connection::handle_read, this->shared_from_this(), - boost::asio::placeholders::error, - boost::asio::placeholders::bytes_transferred)); + std::bind(&connection::handle_read, this->shared_from_this(), + std::placeholders::_1, + std::placeholders::_2)); } /// Decode distributed Erlang message. The message must be fully @@ -357,14 +348,14 @@ handle_read(const boost::system::error_code& err, size_t bytes_transferred) /// Note: TICK message is represented by msg type = 0, in this case \a a_cntrl_msg /// and \a a_msg are invalid. /// @return message type +/// @throws err_decode_exception template int connection:: -transport_msg_decode(const char *mbuf, int len, transport_msg& a_tm) - throw(err_decode_exception) +transport_msg_decode(const char *mbuf, size_t len, transport_msg& a_tm) { const char* s = mbuf; int version; - int index = 0; + uintptr_t index = 0; if (unlikely(len == 0)) // This is TICK message return ERL_TICK; @@ -372,29 +363,32 @@ transport_msg_decode(const char *mbuf, int len, transport_msg& a_tm) /* now decode header */ /* pass-through, version, control tuple header, control message type */ if (unlikely(get8(s) != ERL_PASS_THROUGH)) { - int n = len < 65 ? len : 64; - std::string s = std::string("Missing pass-throgh flag in message") + size_t n = len < 65 ? len : 64; + std::string str = std::string("Missing pass-through flag in message") + to_binary_string(mbuf, n); - throw err_decode_exception(s, len); + throw err_decode_exception(str, index, (long)len); } - if (unlikely(ei_decode_version(s,&index,&version) || version != ERL_VERSION_MAGIC)) - throw err_decode_exception("Invalid control message magic number", version); + if (unlikely(ei_decode_version(s, (int*)&index, &version) || version != ERL_VERSION_MAGIC)) + throw err_decode_exception("Invalid control message magic number", index, version); tuple cntrl(s, index, len, m_allocator); - int msgtype = cntrl[0].to_long(); + long t = cntrl[0].to_long(); + BOOST_ASSERT(t <= INT_MAX); + int msgtype = (int)t; if (unlikely(msgtype <= ERL_TICK) || unlikely(msgtype > ERL_MONITOR_P_EXIT)) - throw err_decode_exception("Invalid message type", msgtype); + throw err_decode_exception("Invalid message type", index, msgtype); static const uint32_t types_with_payload = 1 << ERL_SEND | 1 << ERL_REG_SEND | 1 << ERL_SEND_TT | 1 << ERL_REG_SEND_TT; if (likely((1 << msgtype) & types_with_payload)) { - if (unlikely(ei_decode_version(s,&index,&version)) || unlikely((version != ERL_VERSION_MAGIC))) - throw err_decode_exception("Invalid message magic number", version); + BOOST_ASSERT(index <= INT_MAX); + if (unlikely(ei_decode_version(s, (int*)&index, &version)) || unlikely((version != ERL_VERSION_MAGIC))) + throw err_decode_exception("Invalid message magic number", index, version); eterm msg(s, index, len, m_allocator); a_tm.set(msgtype, cntrl, &msg); @@ -464,32 +458,34 @@ send(const transport_msg& a_msg) bool l_has_msg= a_msg.has_msg(); size_t cntrl_sz = l_cntrl.encode_size(0, true); size_t msg_sz = l_has_msg ? a_msg.msg().encode_size(0, true) : 0; - size_t len = cntrl_sz + msg_sz + 1 /*passthrough*/ + 4 /*len*/; - char* data = allocate(len); + size_t sz = cntrl_sz + msg_sz + 1 /*passthrough*/ + 4 /*len*/; + char* data = allocate(sz); char* s = data; - put32be(s, len-4); + BOOST_ASSERT(sz-4 <= UINT32_MAX); + uint32_t len = (uint32_t)sz - 4; + put32be(s, len); *s++ = ERL_PASS_THROUGH; l_cntrl.encode(s, cntrl_sz, 0, true); if (l_has_msg) a_msg.msg().encode(s + cntrl_sz, msg_sz, 0, true); if (unlikely(verbose() >= VERBOSE_MESSAGE)) { - std::stringstream s; - s << "SEND cntrl=" - << l_cntrl.to_string() << (l_has_msg ? ", msg=" : "") - << (l_has_msg ? a_msg.msg().to_string() : std::string("")); - m_handler->report_status(REPORT_INFO, s.str()); + std::stringstream ss; + ss << "SEND cntrl=" + << l_cntrl.to_string() << (l_has_msg ? ", msg=" : "") + << (l_has_msg ? a_msg.msg().to_string() : std::string("")); + m_handler->report_status(REPORT_INFO, ss.str()); } //if (unlikely(verbose() >= VERBOSE_WIRE)) - // std::cout << "SEND " << len << " bytes " << to_binary_string(data, len) << std::endl; + // std::cout << "SEND " << sz << " bytes " << to_binary_string(data, sz) << std::endl; - boost::asio::const_buffer b(data, len); + boost::asio::const_buffer b(data, sz); m_io_service.post( - boost::bind(&connection::do_write, this->shared_from_this(), b)); + std::bind(&connection::do_write, this->shared_from_this(), b)); } } // namespace connect -} // namespace EIXX_NAMESPACE +} // namespace eixx #endif // _EIXX_TRANSPORT_OTP_CONNECTION_IPP_ diff --git a/include/eixx/connect/transport_otp_connection_tcp.hpp b/include/eixx/connect/transport_otp_connection_tcp.hpp index 5ae4c42..05e0b79 100644 --- a/include/eixx/connect/transport_otp_connection_tcp.hpp +++ b/include/eixx/connect/transport_otp_connection_tcp.hpp @@ -9,23 +9,19 @@ /* ***** BEGIN LICENSE BLOCK ***** -This file is part of the eixx (Erlang C++ Interface) library. +Copyright 2010 Serge Aleynikov -Copyright (c) 2010 Serge Aleynikov +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. + http://www.apache.org/licenses/LICENSE-2.0 -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. ***** END LICENSE BLOCK ***** */ @@ -35,43 +31,14 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include #include -#include #ifdef HAVE_CONFIG_H #include #endif -#ifdef EIXX_HAVE_EI_EPMD -extern "C" { - -#include // see erl_interface/src -#include // ERL_VERSION_MAGIC -#include // see erl_interface/src - -} -#endif - -namespace EIXX_NAMESPACE { +namespace eixx { namespace connect { -#ifndef EIXX_HAVE_EI_EPMD -// These constants are not exposed by EI headers: -static const char ERL_VERSION_MAGIC = 131; -static const short EPMD_PORT = 4369; -static const int EPMDBUF = 512; -static const char EI_EPMD_PORT2_REQ = 122; -static const char EI_EPMD_PORT2_RESP = 119; -static const char EI_DIST_HIGH = 5; -static const int DFLAG_PUBLISHED = 1; -static const int DFLAG_ATOM_CACHE = 2; -static const int DFLAG_EXTENDED_REFERENCES = 4; -static const int DFLAG_DIST_MONITOR = 8; -static const int DFLAG_FUN_TAGS = 16; -static const int DFLAG_NEW_FUN_TAGS = 0x80; -static const int DFLAG_EXTENDED_PIDS_PORTS = 0x100; -static const int DFLAG_NEW_FLOATS = 0x800; -#endif - //---------------------------------------------------------------------------- /// TCP connection channel //---------------------------------------------------------------------------- @@ -81,7 +48,7 @@ class tcp_connection { public: typedef connection base_t; - + tcp_connection(boost::asio::io_service& a_svc, Handler* a_h, const Alloc& a_alloc) : connection(TCP, a_svc, a_h, a_alloc) , m_socket(a_svc) @@ -95,7 +62,8 @@ class tcp_connection void stop(const boost::system::error_code& e) { if (this->handler()->verbose() >= VERBOSE_TRACE) std::cout << "Calling connection_tcp::stop(" << e.message() << ')' << std::endl; - m_socket.close(); + boost::system::error_code ec; + m_socket.close(ec); m_state = CS_INIT; base_t::stop(e); } @@ -106,7 +74,15 @@ class tcp_connection return s.str(); } - int native_socket() { return m_socket.native(); } + int native_socket() { +#if BOOST_VERSION >= 104700 + return m_socket.native_handle(); +#else + return m_socket.native(); +#endif + } + + uint64_t remote_flags() const { return m_remote_flags; } private: /// Authentication state @@ -139,16 +115,16 @@ class tcp_connection const char* m_node_rd; char* m_node_wr; uint16_t m_dist_version; + uint64_t m_remote_flags; uint32_t m_remote_challenge; uint32_t m_our_challenge; - void connect(const std::string& a_this_node, - const std::string& a_remote_node, const std::string& a_cookie) - throw(std::runtime_error); + /// @throws std::runtime_error + void connect(uint32_t a_this_creation, atom a_this_node, atom a_remote_nodename, atom a_cookie); boost::shared_ptr > shared_from_this() { boost::shared_ptr > p = base_t::shared_from_this(); - return *reinterpret_cast >*>(&p); + return boost::reinterpret_pointer_cast >(p); } /// Set the socket to non-blocking mode and issue on_connect() callback. @@ -159,10 +135,20 @@ class tcp_connection void start(); std::string remote_alivename() const { - return this->remote_node().substr(0, this->remote_node().find('@')); + auto s = this->remote_nodename().to_string(); +#ifndef NDEBUG + auto n = s.find('@'); +#endif + BOOST_ASSERT(n != std::string::npos); + return s.substr(0, s.find('@')); } std::string remote_hostname() const { - return this->remote_node().substr(this->remote_node().find('@')+1); + auto s = this->remote_nodename().to_string(); +#ifndef NDEBUG + auto n = s.find('@'); +#endif + BOOST_ASSERT(n != std::string::npos); + return s.substr(s.find('@')+1); } void handle_resolve( @@ -194,16 +180,16 @@ class tcp_connection uint32_t gen_challenge(void); void gen_digest(unsigned challenge, const char cookie[], uint8_t digest[16]); - uint32_t md_32(char* string, int length); + uint32_t md_32(char* string, size_t length); }; } // namespace connect -} // namespace EIXX_NAMESPACE +} // namespace eixx //------------------------------------------------------------------------------ // connection_tcp implementation //------------------------------------------------------------------------------ -#include +#include #endif // _EIXX_TRANSPORT_OTP_CONNECTION_TCP_HPP_ diff --git a/include/eixx/connect/transport_otp_connection_tcp.hxx b/include/eixx/connect/transport_otp_connection_tcp.hxx new file mode 100644 index 0000000..198ac44 --- /dev/null +++ b/include/eixx/connect/transport_otp_connection_tcp.hxx @@ -0,0 +1,943 @@ +//---------------------------------------------------------------------------- +/// \file connection_tcp.hxx +//---------------------------------------------------------------------------- +/// \brief Implementation of TCP connectivity transport with an Erlang node. +//---------------------------------------------------------------------------- +// Copyright (c) 2010 Serge Aleynikov +// Created: 2010-09-11 +//---------------------------------------------------------------------------- +/* +***** BEGIN LICENSE BLOCK ***** + +Copyright 2010 Serge Aleynikov + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +***** END LICENSE BLOCK ***** +*/ + +#ifndef _EIXX_CONNECTION_TCP_IPP_ +#define _EIXX_CONNECTION_TCP_IPP_ + +#include +#include + +namespace eixx { +namespace connect { + +//------------------------------------------------------------------------------ +// connection_tcp implementation +//------------------------------------------------------------------------------ + +template +void tcp_connection::start() +{ +#if BOOST_VERSION >= 104700 + m_socket.non_blocking(true); +#else + boost::asio::ip::tcp::socket::non_blocking_io nb(true); + m_socket.io_control(nb); +#endif + + base_t::start(); // trigger on_connect callback +} + +template +void tcp_connection::connect( + uint32_t a_this_creation, + atom a_this_node, atom a_remote_node, atom a_cookie) +{ + using boost::asio::ip::tcp; + + BOOST_ASSERT(a_this_node.to_string().find('@') != std::string::npos); + + if (a_remote_node.to_string().find('@') == std::string::npos) + THROW_RUNTIME_ERROR("Invalid format of remote_node: " << a_remote_node); + + base_t::connect(a_this_creation, a_this_node, a_remote_node, a_cookie); + + //boost::system::error_code err = boost::asio::error::host_not_found; + std::stringstream es; + + boost::system::error_code ec; + m_socket.close(ec); + + // First resolve remote host name and connect to EPMD to find out the + // node's port number. + + const char* epmd_port_s = getenv("ERL_EPMD_PORT"); + + auto epmd_port = (epmd_port_s != NULL) ? epmd_port_s : std::to_string(EPMD_PORT); + auto host = remote_hostname(); + + tcp::resolver::query q(host, epmd_port); + m_state = CS_WAIT_RESOLVE; + auto pthis = this->shared_from_this(); + m_resolver.async_resolve(q, [pthis](auto& err, auto& ep_iterator) { + pthis->handle_resolve(err, ep_iterator); + }); +} + +template +void tcp_connection::handle_resolve( + const boost::system::error_code& err, boost::asio::ip::tcp::resolver::iterator ep_iterator) +{ + BOOST_ASSERT(m_state == CS_WAIT_RESOLVE); + if (err) { + std::stringstream ss; + ss << "Error resolving address of node '" + << this->remote_nodename() << "': " << err.message(); + this->handler()->on_connect_failure(this, ss.str()); + return; + } + // Attempt a connection to the first endpoint in the list. Each endpoint + // will be tried until we successfully establish a connection. + m_peer_endpoint = *ep_iterator; + m_state = CS_WAIT_EPMD_CONNECT; + auto pthis = this->shared_from_this(); + auto epnext = ++ep_iterator; + m_socket.async_connect(m_peer_endpoint, [pthis, epnext](auto& a_err) { + pthis->handle_epmd_connect(a_err, epnext); + }); +} + +template +void tcp_connection::handle_epmd_connect( + const boost::system::error_code& err, boost::asio::ip::tcp::resolver::iterator ep_iterator) +{ + BOOST_ASSERT(m_state == CS_WAIT_EPMD_CONNECT); + if (!err) { + // The connection was successful. Send the request. + std::string alivename = remote_alivename(); + char* w = m_buf_epmd; + size_t sz = alivename.size(); + BOOST_ASSERT(sz < UINT16_MAX); + uint16_t len = (uint16_t)sz + 1; + put16be(w, len); + put8(w,EI_EPMD_PORT2_REQ); + strncpy(w, alivename.c_str(), sz); + + if (this->handler()->verbose() >= VERBOSE_TRACE) { + std::stringstream ss; + ss << "-> sending epmd port req for '" << alivename << "': " + << to_binary_string(m_buf_epmd, len+2); + this->handler()->report_status(REPORT_INFO, ss.str()); + } + + m_state = CS_WAIT_EPMD_WRITE_DONE; + auto pthis = this->shared_from_this(); + /* + boost::asio::async_write(m_socket, boost::asio::buffer(m_buf_epmd, len+2), + [pthis](auto& err) { pthis->handle_epmd_write(err); }); + */ + boost::asio::async_write(m_socket, boost::asio::buffer(m_buf_epmd, len+2), + std::bind(&tcp_connection::handle_epmd_write, pthis, + std::placeholders::_1)); + } else if (ep_iterator != boost::asio::ip::tcp::resolver::iterator()) { + // The connection failed. Try the next endpoint in the list. + boost::system::error_code ec; + m_socket.close(ec); + m_peer_endpoint = *ep_iterator; + auto pthis = this->shared_from_this(); + auto epnext = ++ep_iterator; + m_socket.async_connect(m_peer_endpoint, + [pthis, epnext](auto& a_err) { + pthis->handle_epmd_connect(a_err, epnext); + }); + } else { + std::stringstream ss; + ss << "Error connecting to epmd at host '" + << this->remote_nodename() << "': " << err.message(); + this->handler()->on_connect_failure(this, ss.str()); + boost::system::error_code ec; + m_socket.close(ec); + } +} + +template +void tcp_connection::handle_epmd_write(const boost::system::error_code& err) +{ + BOOST_ASSERT(m_state == CS_WAIT_EPMD_WRITE_DONE); + if (err) { + std::stringstream ss; + ss << "Error writing to epmd at host '" + << this->remote_nodename() << "': " << err.message(); + this->handler()->on_connect_failure(this, ss.str()); + boost::system::error_code ec; + m_socket.close(ec); + return; + } + + m_state = CS_WAIT_EPMD_REPLY; + m_epmd_wr = m_buf_epmd; + boost::asio::async_read(m_socket, boost::asio::buffer(m_buf_epmd, sizeof(m_buf_epmd)), + boost::asio::transfer_at_least(2), + std::bind(&tcp_connection::handle_epmd_read_header, shared_from_this(), + std::placeholders::_1, + std::placeholders::_2)); +} + +template +void tcp_connection::handle_epmd_read_header( + const boost::system::error_code& err, size_t bytes_transferred) +{ + BOOST_ASSERT(m_state == CS_WAIT_EPMD_REPLY); + if (err) { + std::stringstream ss; + ss << "Error reading response from epmd at host '" + << this->remote_nodename() << "': " << err.message(); + this->handler()->on_connect_failure(this, ss.str()); + boost::system::error_code ec; + m_socket.close(ec); + return; + } + + const char* l_epmd_rd = m_buf_epmd; + m_expect_size = 8; + + int res = get8(l_epmd_rd); + + if (res != EI_EPMD_PORT2_RESP) { // response type + std::stringstream ss; + ss << "Error unknown response from epmd at host '" + << this->remote_nodename() << "': " << res; + this->handler()->on_connect_failure(this, ss.str()); + boost::system::error_code ec; + m_socket.close(ec); + return; + } + + int n = get8(l_epmd_rd); + + if (this->handler()->verbose() >= VERBOSE_TRACE) { + std::stringstream ss; + ss << "<- response from epmd: " << n + << " (" << (n ? "failed" : "ok") << ')'; + this->handler()->report_status(REPORT_INFO, ss.str()); + } + + if (n) { // Got negative response + std::stringstream ss; + ss << "Node " << this->remote_nodename() << " not known to epmd!"; + this->handler()->on_connect_failure(this, ss.str()); + boost::system::error_code ec; + m_socket.close(ec); + return; + } + + m_epmd_wr = m_buf_epmd + bytes_transferred; + + size_t got_bytes = bytes_transferred - 2; + size_t need_bytes = m_expect_size > got_bytes ? m_expect_size - got_bytes : 0; + if (need_bytes > 0) { + boost::asio::async_read(m_socket, + boost::asio::buffer(m_epmd_wr, sizeof(m_buf_epmd)-bytes_transferred), + boost::asio::transfer_at_least(need_bytes), + std::bind(&tcp_connection::handle_epmd_read_body, shared_from_this(), + std::placeholders::_1, + std::placeholders::_2)); + } else { + handle_epmd_read_body(boost::system::error_code(), 0); + } +} + +template +void tcp_connection::handle_epmd_read_body( + const boost::system::error_code& err, size_t bytes_transferred) +{ + BOOST_ASSERT(m_state == CS_WAIT_EPMD_REPLY); + if (err) { + std::stringstream ss; + ss << "Error reading response body from epmd at host '" + << this->remote_nodename() << "': " << err.message(); + this->handler()->on_connect_failure(this, ss.str()); + boost::system::error_code ec; + m_socket.close(ec); + return; + } + + m_epmd_wr += bytes_transferred; + #ifndef NDEBUG + auto got_bytes = m_epmd_wr - m_buf_epmd; + #endif + BOOST_ASSERT(got_bytes >= 10); + + const char* l_epmd_rd = m_buf_epmd + 2; +#if BOOST_VERSION >= 107600 + boost::asio::ip::port_type port = get16be(l_epmd_rd); +#else + uint16_t port = get16be(l_epmd_rd); +#endif + int ntype = get8(l_epmd_rd); + int proto = get8(l_epmd_rd); + uint16_t dist_high = get16be(l_epmd_rd); + uint16_t dist_low = get16be(l_epmd_rd); + m_dist_version = (dist_high > EI_DIST_HIGH ? EI_DIST_HIGH : dist_high); + + if (this->handler()->verbose() >= VERBOSE_TRACE) { + std::stringstream ss; + ss << "<- epmd returned: port=" << port << ",ntype=" << ntype + << ",proto=" << proto << ",dist_high=" << dist_high + << ",dist_low=" << dist_low; + this->handler()->report_status(REPORT_INFO, ss.str()); + } + + m_peer_endpoint.port(port); + m_peer_endpoint.address( m_socket.remote_endpoint().address() ); + boost::system::error_code ec; + m_socket.close(ec); + + if (m_dist_version <= 4) { + std::stringstream ss; + ss << "Incompatible version " << m_dist_version + << " of remote node '" << this->remote_nodename() << "'"; + this->handler()->on_connect_failure(this, ss.str()); + return; + } + + // Change the endpoint's port to the one resolved from epmd + m_state = CS_WAIT_CONNECT; + + m_socket.async_connect(m_peer_endpoint, + std::bind(&tcp_connection::handle_connect, shared_from_this(), + std::placeholders::_1)); +} + +template +void tcp_connection::handle_connect(const boost::system::error_code& err) +{ + BOOST_ASSERT(m_state == CS_WAIT_CONNECT); + if (err) { + std::stringstream ss; + ss << "Cannot connect to node " << this->remote_nodename() + << " at port " << m_peer_endpoint.port() << ": " << err.message(); + this->handler()->on_connect_failure(this, ss.str()); + boost::system::error_code ec; + m_socket.close(ec); + return; + } + + if (this->handler()->verbose() >= VERBOSE_TRACE) { + std::stringstream ss; + ss << "<- TCP_OPEN (ok) from node '" << this->remote_nodename() << "'"; + this->handler()->report_status(REPORT_INFO, ss.str()); + } + + m_our_challenge = gen_challenge(); + + auto flags = LOCAL_FLAGS; +#ifdef EI_DIST_5 + char tag = (m_dist_version == EI_DIST_5) ? 'n' : 'N'; +#else + char tag = 'n'; +#endif +#ifdef DFLAG_NAME_ME + if (this->local_nodename().empty()) { + /* dynamic node name */ + tag = 'N'; /* presume ver 6 */ + flags |= DFLAG_NAME_ME; + } +#endif + + uint16_t siz; + if (tag == 'n') + siz = 2 + 1 + 2 + 4 + this->local_nodename().size(); + else /* tag == 'N' */ + siz = 2 + 1 + 8 + 4 + 2 + this->local_nodename().size(); + + if (siz > sizeof(m_buf_node)) { + std::stringstream ss; + ss << "-> SEND_NAME (error) nodename too long: " << this->local_nodename() + << " [" << __FILE__ << ':' << __LINE__ << ']'; + this->handler()->on_connect_failure(this, ss.str()); + boost::system::error_code ec; + m_socket.close(ec); + return; + } + + char* w = m_buf_node; + put16be(w, siz - 2); + put8(w, static_cast(tag)); + if (tag == 'n') { +#ifdef EI_DIST_5 + m_dist_version = EI_DIST_5; + put16be(w, EI_DIST_5); /* spec demands ver==5 */ +#else + m_dist_version = EI_DIST_LOW; + put16be(w, EI_DIST_LOW); /* spec demands ver==5 */ +#endif + put32be(w, flags & 0xffffffff /* FlagsLow */); + } else { /* tag == 'N' */ + put64be(w, flags); + put32be(w, this->local_creation()); + put16be(w, this->local_nodename().size()); + } + memcpy(w, this->local_nodename().c_str(), this->local_nodename().size()); + + if (this->handler()->verbose() >= VERBOSE_TRACE) { + std::stringstream ss; + ss << "-> SEND_NAME sending creation=" << this->local_creation() + << " to node '" << this->remote_nodename() << "': " + << to_binary_string(m_buf_node, siz); + this->handler()->report_status(REPORT_INFO, ss.str()); + } + + m_state = CS_WAIT_WRITE_CHALLENGE_DONE; + boost::asio::async_write(m_socket, boost::asio::buffer(m_buf_node, siz), + std::bind(&tcp_connection::handle_write_name, shared_from_this(), + std::placeholders::_1)); +} + +template +void tcp_connection::handle_write_name(const boost::system::error_code& err) +{ + BOOST_ASSERT(m_state == CS_WAIT_WRITE_CHALLENGE_DONE); + if (err) { + std::stringstream ss; + ss << "-> SEND_NAME (error) sending name to node '" + << this->remote_nodename() << "': " << err.message(); + this->handler()->on_connect_failure(this, ss.str()); + boost::system::error_code ec; + m_socket.close(ec); + return; + } + m_state = CS_WAIT_STATUS; + boost::asio::async_read(m_socket, boost::asio::buffer(m_buf_node, sizeof(m_buf_node)), + boost::asio::transfer_at_least(2), + std::bind(&tcp_connection::handle_read_status_header, shared_from_this(), + std::placeholders::_1, + std::placeholders::_2)); +} + +template +void tcp_connection::handle_read_status_header( + const boost::system::error_code& err, size_t bytes_transferred) +{ + BOOST_ASSERT(m_state == CS_WAIT_STATUS); + if (err) { + std::stringstream ss; + ss << "<- RECV_STATUS (error) reading status header from node '" + << this->remote_nodename() << "': " << err.message(); + this->handler()->on_connect_failure(this, ss.str()); + boost::system::error_code ec; + m_socket.close(ec); + return; + } + + m_node_rd = m_buf_node; + m_expect_size = get16be(m_node_rd); + + if (m_expect_size > static_cast(MAXNODELEN + 8) || m_expect_size > static_cast((m_buf_node+1) - m_node_rd)) { + std::stringstream ss; + ss << "<- RECV_STATUS (error) in status length from node '" + << this->remote_nodename() << "': " << m_expect_size; + this->handler()->on_connect_failure(this, ss.str()); + boost::system::error_code ec; + m_socket.close(ec); + return; + } + + m_node_wr = m_buf_node + bytes_transferred; + + size_t got_bytes = static_cast(m_node_wr - m_node_rd); + size_t need_bytes = m_expect_size > got_bytes ? m_expect_size - got_bytes : 0; + if (need_bytes > 0) { + boost::asio::async_read(m_socket, + boost::asio::buffer(m_node_wr, static_cast((m_buf_node+1) - m_node_wr)), + boost::asio::transfer_at_least(need_bytes), + std::bind(&tcp_connection::handle_read_status_body, shared_from_this(), + std::placeholders::_1, + std::placeholders::_2)); + } else { + handle_read_status_body(boost::system::error_code(), 0); + } +} + +template +void tcp_connection::handle_read_status_body( + const boost::system::error_code& err, size_t bytes_transferred) +{ + BOOST_ASSERT(m_state == CS_WAIT_STATUS); + if (err) { + std::stringstream ss; + ss << "<- RECV_STATUS (error) reading status body from node '" + << this->remote_nodename() << "': " << err.message(); + this->handler()->on_connect_failure(this, ss.str()); + boost::system::error_code ec; + m_socket.close(ec); + return; + } + + m_node_wr += bytes_transferred; + size_t got_bytes = static_cast(m_node_wr - m_node_rd); + BOOST_ASSERT(got_bytes >= 3); + + if (!this->local_nodename().empty()) { + if (memcmp(m_node_rd, "sok", 3) != 0) { + std::stringstream ss; + ss << "<- RECV_STATUS (error) in auth status from node '" + << this->remote_nodename() << "': " << std::string(m_node_rd, 1, got_bytes); + this->handler()->on_connect_failure(this, ss.str()); + boost::system::error_code ec; + m_socket.close(ec); + return; + } + } else { + /* dynamic node name */ + if (memcmp(m_node_rd, "snamed:", 7) != 0) { + std::stringstream ss; + ss << "<- RECV_STATUS (error) in auth status from node '" + << this->remote_nodename() << "': " << std::string(m_node_rd, 1, got_bytes); + this->handler()->on_connect_failure(this, ss.str()); + boost::system::error_code ec; + m_socket.close(ec); + return; + } + + uint16_t nodename_len = get16be(m_node_rd); + if (nodename_len > MAXNODELEN) { + std::stringstream ss; + ss << "<- RECV_STATUS (error) nodename too long from node '" + << this->remote_nodename() << "': " << nodename_len; + this->handler()->on_connect_failure(this, ss.str()); + boost::system::error_code ec; + m_socket.close(ec); + return; + } + atom l_this_node(m_node_rd, nodename_len); + this->m_this_node = l_this_node; + + uint32_t l_cre = get32be(m_node_rd); + this->m_this_creation = l_cre; + } + + if (this->handler()->verbose() >= VERBOSE_TRACE) { + std::stringstream ss; + ss << "<- RECV_STATUS (ok) from node '" << this->remote_nodename() + << "': version=" << m_dist_version + << ", status=" << std::string(m_node_rd, 1, got_bytes); + this->handler()->report_status(REPORT_INFO, ss.str()); + } + + m_state = CS_WAIT_CHALLENGE; + + // recv challenge + m_node_rd += 3; + got_bytes -= 3; + if (got_bytes >= 2) + handle_read_challenge_header(boost::system::error_code(), 0); + else + boost::asio::async_read(m_socket, boost::asio::buffer(m_node_wr, static_cast((m_buf_node+1) - m_node_wr)), + boost::asio::transfer_at_least(2), + std::bind(&tcp_connection::handle_read_challenge_header, shared_from_this(), + std::placeholders::_1, + std::placeholders::_2)); +} + +template +void tcp_connection::handle_read_challenge_header( + const boost::system::error_code& err, size_t bytes_transferred) +{ + BOOST_ASSERT(m_state == CS_WAIT_CHALLENGE); + if (err) { + std::stringstream ss; + ss << "<- RECV_CHALLENGE (error) reading challenge header from node '" + << this->remote_nodename() << "': " << err.message(); + this->handler()->on_connect_failure(this, ss.str()); + boost::system::error_code ec; + m_socket.close(ec); + return; + } + + m_node_wr += bytes_transferred; + m_expect_size = get16be(m_node_rd); + +#ifdef EI_DIST_5 + unsigned short l_size = (m_dist_version == EI_DIST_5) ? 11 : 19; +#else + unsigned short l_size = 11; +#endif + + if (m_expect_size > static_cast(MAXNODELEN + l_size) || m_expect_size > static_cast((m_buf_node+1) - m_node_rd)) { + std::stringstream ss; + ss << "<- RECV_CHALLENGE (error) in challenge length from node '" + << this->remote_nodename() << "': " << m_expect_size; + this->handler()->on_connect_failure(this, ss.str()); + boost::system::error_code ec; + m_socket.close(ec); + return; + } + + size_t got_bytes = static_cast(m_node_wr - m_node_rd); + size_t need_bytes = m_expect_size > got_bytes ? m_expect_size - got_bytes : 0; + if (need_bytes > 0) { + boost::asio::async_read(m_socket, + boost::asio::buffer(m_node_wr, static_cast((m_buf_node+1) - m_node_wr)), + boost::asio::transfer_at_least(need_bytes), + std::bind(&tcp_connection::handle_read_challenge_body, shared_from_this(), + std::placeholders::_1, + std::placeholders::_2)); + } else { + handle_read_challenge_body(boost::system::error_code(), 0); + } +} + +template +void tcp_connection::handle_read_challenge_body( + const boost::system::error_code& err, size_t bytes_transferred) +{ + BOOST_ASSERT(m_state == CS_WAIT_CHALLENGE); + if (err) { + std::stringstream ss; + ss << "<- RECV_CHALLENGE (error) reading challenge body from node '" + << this->remote_nodename() << "': " << err.message(); + this->handler()->on_connect_failure(this, ss.str()); + boost::system::error_code ec; + m_socket.close(ec); + return; + } + + m_node_wr += bytes_transferred; + #ifndef NDEBUG + size_t got_bytes = static_cast(m_node_wr - m_node_rd); + #endif + BOOST_ASSERT(got_bytes >= m_expect_size); + + const char tag = static_cast(get8(m_node_rd)); + if (tag != 'n' && tag != 'N') { + std::stringstream ss; + ss << "<- RECV_CHALLENGE (error) incorrect tag, " + << "expected 'n' or 'N', got '" << tag << "' from node '" + << this->remote_nodename() << "'"; + this->handler()->on_connect_failure(this, ss.str()); + boost::system::error_code ec; + m_socket.close(ec); + return; + } + + size_t nodename_len; + uint16_t dist_version = m_dist_version; + + // See: https://github.com/erlang/otp/blob/OTP-24.0.5/lib/erl_interface/src/connect/ei_connect.c#L2478 + if (tag == 'n') { /* OLD */ + m_dist_version = get16be(m_node_rd); +#ifdef EI_DIST_5 + if (m_dist_version != EI_DIST_5) { + std::stringstream ss; + ss << "<- RECV_CHALLENGE 'n' (error) incorrect version from node '" + << this->remote_nodename() << "': " << m_dist_version; + this->handler()->on_connect_failure(this, ss.str()); + boost::system::error_code ec; + m_socket.close(ec); + return; + } +#endif + + m_remote_flags = get32be(m_node_rd); + m_remote_challenge = get32be(m_node_rd); + nodename_len = static_cast(m_node_wr - m_node_rd); +#ifndef EI_DIST_6 + } +#else + } else { /* NEW */ + m_dist_version = EI_DIST_6; + m_remote_flags = get64be(m_node_rd); + m_remote_challenge = get32be(m_node_rd); + m_node_rd += 4; /* ignore peer 'creation' */ + nodename_len = get16be(m_node_rd); + + if (nodename_len > static_cast(m_node_wr - m_node_rd)) { + std::stringstream ss; + ss << "<- RECV_CHALLENGE 'N' (error) nodename too long from node '" + << this->remote_nodename() << "': " << nodename_len; + this->handler()->on_connect_failure(this, ss.str()); + boost::system::error_code ec; + m_socket.close(ec); + return; + } + } +#endif + + if (nodename_len > MAXNODELEN) { + std::stringstream ss; + ss << "<- RECV_CHALLENGE (error) nodename too long from node '" + << this->remote_nodename() << "': " << nodename_len; + this->handler()->on_connect_failure(this, ss.str()); + boost::system::error_code ec; + m_socket.close(ec); + return; + } + + if (!(m_remote_flags & DFLAG_EXTENDED_REFERENCES)) { + std::stringstream ss; + ss << "<- RECV_CHALLENGE peer cannot handle extended references"; + this->handler()->on_connect_failure(this, ss.str()); + boost::system::error_code ec; + m_socket.close(ec); + return; + } + + if (!(m_remote_flags & DFLAG_EXTENDED_PIDS_PORTS)) { + std::stringstream ss; + ss << "<- RECV_CHALLENGE peer cannot handle extended pids and ports"; + this->handler()->on_connect_failure(this, ss.str()); + boost::system::error_code ec; + m_socket.close(ec); + return; + } + + if (!(m_remote_flags & DFLAG_NEW_FLOATS)) { + std::stringstream ss; + ss << "<- RECV_CHALLENGE peer cannot handle binary float encoding"; + this->handler()->on_connect_failure(this, ss.str()); + boost::system::error_code ec; + m_socket.close(ec); + return; + } + + if (this->handler()->verbose() >= VERBOSE_TRACE) { + std::stringstream ss; + ss << "<- RECV_CHALLENGE (ok) version=" << m_dist_version + << ", flags=" << m_remote_flags << ", challenge=" << m_remote_challenge; + this->handler()->report_status(REPORT_INFO, ss.str()); + } + + uint8_t our_digest[16]; + gen_digest(m_remote_challenge, this->m_cookie.c_str(), our_digest); + + char* w = m_buf_node; + size_t siz = 0; + + // send complement (if dist_version upgraded in-flight) + if (m_dist_version > dist_version) { + siz += 2 + 1 + 4 + 4; + put16be(w, 9); + put8(w, 'c'); + put32be(w, LOCAL_FLAGS >> 32 /* FlagsHigh */); + put32be(w, this->local_creation()); + } + + // send challenge reply + siz += 2 + 1 + 4 + 16; + put16be(w, 21); + put8(w, 'r'); + put32be(w, m_our_challenge); + memcpy (w, our_digest, 16); + + if (this->handler()->verbose() >= VERBOSE_TRACE) { + std::stringstream ss; + ss << "-> SEND_CHALLENGE_REPLY sending " << siz << " bytes: " + << to_binary_string(m_buf_node, siz); + this->handler()->report_status(REPORT_INFO, ss.str()); + } + + m_state = CS_WAIT_WRITE_CHALLENGE_REPLY_DONE; + boost::asio::async_write(m_socket, boost::asio::buffer(m_buf_node, siz), + std::bind(&tcp_connection::handle_write_challenge_reply, shared_from_this(), + std::placeholders::_1)); +} + +template +void tcp_connection::handle_write_challenge_reply(const boost::system::error_code& err) +{ + BOOST_ASSERT(m_state == CS_WAIT_WRITE_CHALLENGE_REPLY_DONE); + if (err) { + std::stringstream ss; + ss << "-> SEND_CHALLENGE_REPLY (error) sending reply to node '" + << this->remote_nodename() << "': " << err.message(); + this->handler()->on_connect_failure(this, ss.str()); + boost::system::error_code ec; + m_socket.close(ec); + return; + } + m_state = CS_WAIT_CHALLENGE_ACK; + boost::asio::async_read(m_socket, boost::asio::buffer(m_buf_node, sizeof(m_buf_node)), + boost::asio::transfer_at_least(2), + std::bind(&tcp_connection::handle_read_challenge_ack_header, shared_from_this(), + std::placeholders::_1, + std::placeholders::_2)); +} + +template +void tcp_connection::handle_read_challenge_ack_header( + const boost::system::error_code& err, size_t bytes_transferred) +{ + BOOST_ASSERT(m_state == CS_WAIT_CHALLENGE_ACK); + if (err) { + std::stringstream ss; + ss << "<- RECV_CHALLENGE_ACK (error) reading ack header from node '" + << this->remote_nodename() << "': " + << (err == boost::asio::error::eof ? "Possibly bad cookie?" : err.message()); + this->handler()->on_connect_failure(this, ss.str()); + boost::system::error_code ec; + m_socket.close(ec); + return; + } + + m_node_rd = m_buf_node; + m_expect_size = get16be(m_node_rd); + + if (m_expect_size > sizeof(m_buf_node) - 2) { + std::stringstream ss; + ss << "<- RECV_CHALLENGE_ACK (error) in ack length from node '" + << this->remote_nodename() << "': " << m_expect_size; + this->handler()->on_connect_failure(this, ss.str()); + boost::system::error_code ec; + m_socket.close(ec); + return; + } + + m_node_wr = m_buf_node + bytes_transferred; + + size_t got_bytes = static_cast(m_node_wr - m_node_rd); + size_t need_bytes = m_expect_size > got_bytes ? m_expect_size - got_bytes : 0; + if (need_bytes > 0) { + boost::asio::async_read(m_socket, + boost::asio::buffer(m_node_wr, static_cast((m_buf_node+1)-m_node_wr)), + boost::asio::transfer_at_least(need_bytes), + std::bind(&tcp_connection::handle_read_challenge_ack_body, + shared_from_this(), + std::placeholders::_1, + std::placeholders::_2)); + } else { + handle_read_challenge_ack_body(boost::system::error_code(), 0); + } +} + +template +void tcp_connection::handle_read_challenge_ack_body( + const boost::system::error_code& err, size_t bytes_transferred) +{ + BOOST_ASSERT(m_state == CS_WAIT_CHALLENGE_ACK); + if (err) { + std::stringstream ss; + ss << "<- RECV_CHALLENGE_ACK (error) reading ack body from node '" + << this->remote_nodename() << "': " << err.message(); + this->handler()->on_connect_failure(this, ss.str()); + boost::system::error_code ec; + m_socket.close(ec); + return; + } + + m_node_wr += bytes_transferred; + #ifndef NDEBUG + size_t got_bytes = static_cast(m_node_wr - m_node_rd); + #endif + BOOST_ASSERT(got_bytes >= m_expect_size); + + const char tag = static_cast(get8(m_node_rd)); + if (this->handler()->verbose() >= VERBOSE_TRACE) { + std::stringstream ss; + ss << "<- RECV_CHALLENGE_ACK received (tag=" << tag << "): " + << to_binary_string(m_node_rd, m_expect_size-1); + this->handler()->report_status(REPORT_INFO, ss.str()); + } + + if (tag != 'a') { + std::stringstream ss; + ss << "<- RECV_CHALLENGE_ACK incorrect tag from '" + << this->remote_nodename() << "', expected 'a' got '" << tag << "'"; + this->handler()->on_connect_failure(this, ss.str()); + boost::system::error_code ec; + m_socket.close(ec); + return; + } + + char her_digest[16], expected_digest[16]; + memcpy(her_digest, m_node_rd, 16); + gen_digest(m_our_challenge, this->m_cookie.c_str(), (uint8_t*)expected_digest); + if (memcmp(her_digest, expected_digest, 16) != 0) { + std::stringstream ss; + ss << "<- RECV_CHALLENGE_ACK authorization failure for node '" + << this->remote_nodename() << "'!"; + this->handler()->on_connect_failure(this, ss.str()); + boost::system::error_code ec; + m_socket.close(ec); + return; + } + + m_socket.set_option(boost::asio::ip::tcp::no_delay(true)); + m_socket.set_option(boost::asio::socket_base::keep_alive(true)); + + m_state = CS_CONNECTED; + + this->start(); +} + +template +uint32_t tcp_connection::gen_challenge(void) +{ +#if defined(__WIN32__) + struct { + SYSTEMTIME tv; + DWORD cpu; + int pid; + } s; + GetSystemTime(&s.tv); + s.cpu = GetTickCount(); + s.pid = getpid(); + +#else + struct { + struct timeval tv; + clock_t cpu; + pid_t pid; + long hid; + uid_t uid; + gid_t gid; + struct utsname name; + } s; + + gettimeofday(&s.tv, 0); + uname(&s.name); + s.cpu = clock(); + s.pid = getpid(); + s.hid = gethostid(); + s.uid = getuid(); + s.gid = getgid(); +#endif + return md_32((char*) &s, sizeof(s)); +} + +template +uint32_t tcp_connection:: +md_32(char* string, size_t length) +{ + BOOST_STATIC_ASSERT(MD5_DIGEST_LENGTH == 16); + union { + char c[MD5_DIGEST_LENGTH]; + unsigned x[4]; + } digest; + MD5((unsigned char *) string, length, (unsigned char *) digest.c); + return (digest.x[0] ^ digest.x[1] ^ digest.x[2] ^ digest.x[3]); + return (digest.x[0] ^ digest.x[1] ^ digest.x[2] ^ digest.x[3]); +} + +template +void tcp_connection:: +gen_digest(unsigned challenge, const char cookie[], uint8_t digest[16]) +{ + MD5_CTX c; + char chbuf[21]; + snprintf(chbuf, sizeof(chbuf), "%u", challenge); + MD5_Init(&c); + MD5_Update(&c, (unsigned char *) cookie, (uint32_t) strlen(cookie)); + MD5_Update(&c, (unsigned char *) chbuf, (uint32_t) strlen(chbuf)); + MD5_Final(digest, &c); +} + +} // namespace connect +} // namespace eixx + +#endif // _EIXX_CONNECTION_TCP_IPP_ + diff --git a/include/eixx/connect/transport_otp_connection_tcp.ipp b/include/eixx/connect/transport_otp_connection_tcp.ipp deleted file mode 100644 index 973be77..0000000 --- a/include/eixx/connect/transport_otp_connection_tcp.ipp +++ /dev/null @@ -1,715 +0,0 @@ -//---------------------------------------------------------------------------- -/// \file connection_tcp.ipp -//---------------------------------------------------------------------------- -/// \brief Implementation of TCP connectivity transport with an Erlang node. -//---------------------------------------------------------------------------- -// Copyright (c) 2010 Serge Aleynikov -// Created: 2010-09-11 -//---------------------------------------------------------------------------- -/* -***** BEGIN LICENSE BLOCK ***** - -This file is part of the eixx (Erlang C++ Interface) library. - -Copyright (c) 2010 Serge Aleynikov - -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -***** END LICENSE BLOCK ***** -*/ - -#ifndef _EIXX_CONNECTION_TCP_IPP_ -#define _EIXX_CONNECTION_TCP_IPP_ - -#include -#include - -namespace EIXX_NAMESPACE { -namespace connect { - -//------------------------------------------------------------------------------ -// connection_tcp implementation -//------------------------------------------------------------------------------ - -template -void tcp_connection::start() -{ - boost::asio::ip::tcp::socket::non_blocking_io nb(true); - m_socket.io_control(nb); - - base_t::start(); // trigger on_connect callback -} - -template -void tcp_connection::connect( - const std::string& a_this_node, const std::string& a_remote_node, const std::string& a_cookie) - throw(std::runtime_error) -{ - using boost::asio::ip::tcp; - - base_t::connect(a_this_node, a_remote_node, a_cookie); - - if (a_this_node.find('@', 0) == std::string::npos) - THROW_RUNTIME_ERROR("Invalid format of this_node: " << a_this_node); - if (a_remote_node.find('@', 0) == std::string::npos) - THROW_RUNTIME_ERROR("Invalid format of remote_node: " << a_remote_node); - - //boost::system::error_code err = boost::asio::error::host_not_found; - std::stringstream es; - - m_socket.close(); - - // First resolve remote host name and connect to EPMD to find out the - // node's port number. - - const char* epmd_port_s = getenv("ERL_EPMD_PORT"); - std::stringstream str; - str << EPMD_PORT; - - const char* epmd_port = (epmd_port_s != NULL) ? epmd_port_s : str.str().c_str(); - - tcp::resolver::query q(remote_hostname(), epmd_port); - m_state = CS_WAIT_RESOLVE; - m_resolver.async_resolve(q, - boost::bind(&tcp_connection::handle_resolve, shared_from_this(), - boost::asio::placeholders::error, - boost::asio::placeholders::iterator)); -} - -template -void tcp_connection::handle_resolve( - const boost::system::error_code& err, boost::asio::ip::tcp::resolver::iterator ep_iterator) -{ - BOOST_ASSERT(m_state == CS_WAIT_RESOLVE); - if (err) { - std::stringstream str; str << "Error resolving address of node '" - << this->remote_node() << "': " << err.message(); - this->handler()->on_connect_failure(this, str.str()); - return; - } - // Attempt a connection to the first endpoint in the list. Each endpoint - // will be tried until we successfully establish a connection. - m_peer_endpoint = *ep_iterator; - m_state = CS_WAIT_EPMD_CONNECT; - m_socket.async_connect(m_peer_endpoint, - boost::bind(&tcp_connection::handle_epmd_connect, shared_from_this(), - boost::asio::placeholders::error, ++ep_iterator)); -} - -template -void tcp_connection::handle_epmd_connect( - const boost::system::error_code& err, boost::asio::ip::tcp::resolver::iterator ep_iterator) -{ - BOOST_ASSERT(m_state == CS_WAIT_EPMD_CONNECT); - if (!err) { - // The connection was successful. Send the request. - std::string alivename = remote_alivename(); - char* w = m_buf_epmd; - int len = alivename.size() + 1; - put16be(w,len); - put8(w,EI_EPMD_PORT2_REQ); - strcpy(w, alivename.c_str()); - - if (this->handler()->verbose() >= VERBOSE_TRACE) { - std::stringstream s; - s << "-> sending epmd port req for '" << alivename << "': " - << to_binary_string(m_buf_epmd, len+2); - this->handler()->report_status(REPORT_INFO, s.str()); - } - - m_state = CS_WAIT_EPMD_WRITE_DONE; - boost::asio::async_write(m_socket, boost::asio::buffer(m_buf_epmd, len+2), - boost::bind(&tcp_connection::handle_epmd_write, shared_from_this(), - boost::asio::placeholders::error)); - } else if (ep_iterator != boost::asio::ip::tcp::resolver::iterator()) { - // The connection failed. Try the next endpoint in the list. - m_socket.close(); - m_peer_endpoint = *ep_iterator; - m_socket.async_connect(m_peer_endpoint, - boost::bind(&tcp_connection::handle_epmd_connect, shared_from_this(), - boost::asio::placeholders::error, ++ep_iterator)); - } else { - std::stringstream str; str << "Error connecting to epmd at host '" - << this->remote_node() << "': " << err.message(); - this->handler()->on_connect_failure(this, str.str()); - m_socket.close(); - } -} - -template -void tcp_connection::handle_epmd_write(const boost::system::error_code& err) -{ - BOOST_ASSERT(m_state == CS_WAIT_EPMD_WRITE_DONE); - if (err) { - std::stringstream str; str << "Error writing to epmd at host '" - << this->remote_node() << "': " << err.message(); - this->handler()->on_connect_failure(this, str.str()); - m_socket.close(); - return; - } - - m_state = CS_WAIT_EPMD_REPLY; - m_epmd_wr = m_buf_epmd; - boost::asio::async_read(m_socket, boost::asio::buffer(m_buf_epmd, sizeof(m_buf_epmd)), - boost::asio::transfer_at_least(2), - boost::bind(&tcp_connection::handle_epmd_read_header, shared_from_this(), - boost::asio::placeholders::error, - boost::asio::placeholders::bytes_transferred)); -} - -template -void tcp_connection::handle_epmd_read_header( - const boost::system::error_code& err, size_t bytes_transferred) -{ - BOOST_ASSERT(m_state == CS_WAIT_EPMD_REPLY); - if (err) { - std::stringstream str; str << "Error reading response from epmd at host '" - << this->remote_node() << "': " << err.message(); - this->handler()->on_connect_failure(this, str.str()); - m_socket.close(); - return; - } - - const char* s = m_buf_epmd; - int res = get8(s); - - if (res != EI_EPMD_PORT2_RESP) { // response type - std::stringstream str; str << "Error unknown response from epmd at host '" - << this->remote_node() << "': " << res; - this->handler()->on_connect_failure(this, str.str()); - m_socket.close(); - return; - } - - int n = get8(s); - - if (this->handler()->verbose() >= VERBOSE_TRACE) { - std::stringstream s; - s << "<- response from epmd: " << n - << " (" << (n ? "failed" : "ok") << ')'; - this->handler()->report_status(REPORT_INFO, s.str()); - } - - if (n) { // Got negative response - std::stringstream str; str << "Node " << this->remote_node() << " not known to epmd!"; - this->handler()->on_connect_failure(this, str.str()); - m_socket.close(); - return; - } - - m_epmd_wr = m_buf_epmd + bytes_transferred; - - int need_bytes = 8 - (bytes_transferred - 2); - if (need_bytes > 0) { - boost::asio::async_read(m_socket, - boost::asio::buffer(m_epmd_wr, sizeof(m_buf_epmd)-bytes_transferred), - boost::asio::transfer_at_least(need_bytes), - boost::bind(&tcp_connection::handle_epmd_read_body, shared_from_this(), - boost::asio::placeholders::error, - boost::asio::placeholders::bytes_transferred)); - } else { - handle_epmd_read_body(boost::system::error_code(), 0); - } -} - -template -void tcp_connection::handle_epmd_read_body( - const boost::system::error_code& err, size_t bytes_transferred) -{ - BOOST_ASSERT(m_state == CS_WAIT_EPMD_REPLY); - if (err) { - std::stringstream str; str << "Error reading response body from epmd at host '" - << this->remote_node() << "': " << err.message(); - this->handler()->on_connect_failure(this, str.str()); - m_socket.close(); - return; - } - - m_epmd_wr += bytes_transferred; - #ifndef BOOST_DISABLE_ASSERTS - int got_bytes = m_epmd_wr - m_buf_epmd; - #endif - BOOST_ASSERT(got_bytes >= 10); - - const char* s = m_buf_epmd + 2; - int port = get16be(s); - int ntype = get8(s); - int proto = get8(s); - uint16_t dist_high = get16be(s); - uint16_t dist_low = get16be(s); - m_dist_version = (dist_high > EI_DIST_HIGH ? EI_DIST_HIGH : dist_high); - - if (this->handler()->verbose() >= VERBOSE_TRACE) { - std::stringstream s; - s << "<- epmd returned: port=" << port << ",ntype=" << ntype - << ",proto=" << proto << ",dist_high=" << dist_high - << ",dist_low=" << dist_low; - this->handler()->report_status(REPORT_INFO, s.str()); - } - - m_peer_endpoint.port(port); - m_peer_endpoint.address( m_socket.remote_endpoint().address() ); - m_socket.close(); - - if (m_dist_version <= 4) { - std::stringstream str; str << "Incompatible version " << m_dist_version - << " of remote node '" << this->remote_node() << "'"; - this->handler()->on_connect_failure(this, str.str()); - return; - } - - // Change the endpoint's port to the one resolved from epmd - m_state = CS_WAIT_CONNECT; - - m_socket.async_connect(m_peer_endpoint, - boost::bind(&tcp_connection::handle_connect, shared_from_this(), - boost::asio::placeholders::error)); -} - -template -void tcp_connection::handle_connect(const boost::system::error_code& err) -{ - BOOST_ASSERT(m_state == CS_WAIT_CONNECT); - if (err) { - std::stringstream str; str << "Cannot connect to node " << this->remote_node() - << " at port " << m_peer_endpoint.port() << ": " << err.message(); - this->handler()->on_connect_failure(this, str.str()); - m_socket.close(); - return; - } - - if (this->handler()->verbose() >= VERBOSE_TRACE) { - std::stringstream s; - s << "<- Connected to node: " << this->remote_node(); - this->handler()->report_status(REPORT_INFO, s.str()); - } - - m_our_challenge = gen_challenge(); - - // send challenge - size_t siz = 2 + 1 + 2 + 4 + this->m_this_node.size(); - - if (siz > sizeof(m_buf_node)) { - std::stringstream str; str << "Node name too long: " << this->this_node() - << " [" << __FILE__ << ':' << __LINE__ << ']'; - this->handler()->on_connect_failure(this, str.str()); - m_socket.close(); - return; - } - - char* w = m_buf_node; - put16be(w, siz - 2); - put8(w, 'n'); - put16be(w, m_dist_version); - put32be(w, ( DFLAG_EXTENDED_REFERENCES - | DFLAG_EXTENDED_PIDS_PORTS - | DFLAG_FUN_TAGS - | DFLAG_NEW_FUN_TAGS - | DFLAG_NEW_FLOATS - | DFLAG_DIST_MONITOR)); - memcpy(w, this->this_node().c_str(), this->this_node().size()); - - if (this->handler()->verbose() >= VERBOSE_TRACE) { - std::stringstream s; - s << "-> sending name " << siz << " bytes:" - << to_binary_string(m_buf_node, siz); - this->handler()->report_status(REPORT_INFO, s.str()); - } - - m_state = CS_WAIT_WRITE_CHALLENGE_DONE; - boost::asio::async_write(m_socket, boost::asio::buffer(m_buf_node, siz), - boost::bind(&tcp_connection::handle_write_name, shared_from_this(), - boost::asio::placeholders::error)); -} - -template -void tcp_connection::handle_write_name(const boost::system::error_code& err) -{ - BOOST_ASSERT(m_state == CS_WAIT_WRITE_CHALLENGE_DONE); - if (err) { - std::stringstream str; str << "Error writing auth challenge to node '" - << this->remote_node() << "': " << err.message(); - this->handler()->on_connect_failure(this, str.str()); - m_socket.close(); - return; - } - m_state = CS_WAIT_STATUS; - boost::asio::async_read(m_socket, boost::asio::buffer(m_buf_node, sizeof(m_buf_node)), - boost::asio::transfer_at_least(2), - boost::bind(&tcp_connection::handle_read_status_header, shared_from_this(), - boost::asio::placeholders::error, - boost::asio::placeholders::bytes_transferred)); -} - -template -void tcp_connection::handle_read_status_header( - const boost::system::error_code& err, size_t bytes_transferred) -{ - BOOST_ASSERT(m_state == CS_WAIT_STATUS); - if (err) { - std::stringstream str; str << "Error reading auth status from node '" - << this->remote_node() << "': " << err.message(); - this->handler()->on_connect_failure(this, str.str()); - m_socket.close(); - return; - } - - m_node_rd = m_buf_node; - size_t len = get16be(m_node_rd); - - if (len != 3) { - std::stringstream str; str << "Node " << this->remote_node() - << " rejected connection with reason: " << std::string(m_node_rd, len); - this->handler()->on_connect_failure(this, str.str()); - m_socket.close(); - return; - } - - m_node_wr = m_buf_node + bytes_transferred; - - int need_bytes = 5 - (bytes_transferred - 2); - if (need_bytes > 0) { - boost::asio::async_read(m_socket, - boost::asio::buffer(m_node_wr, (m_buf_node+1) - m_node_wr), - boost::asio::transfer_at_least(need_bytes), - boost::bind(&tcp_connection::handle_read_status_body, shared_from_this(), - boost::asio::placeholders::error, - boost::asio::placeholders::bytes_transferred)); - } else { - handle_read_status_body(boost::system::error_code(), 0); - } -} - -template -void tcp_connection::handle_read_status_body( - const boost::system::error_code& err, size_t bytes_transferred) -{ - BOOST_ASSERT(m_state == CS_WAIT_STATUS); - if (err) { - std::stringstream str; str << "Error reading auth status body from node '" - << this->remote_node() << "': " << err.message(); - this->handler()->on_connect_failure(this, str.str()); - m_socket.close(); - return; - } - - m_node_wr += bytes_transferred; - int got_bytes = m_node_wr - m_node_rd; - BOOST_ASSERT(got_bytes >= 3); - - if (memcmp(m_node_rd, "sok", 3) != 0) { - std::stringstream str; str << "Error invalid auth status response '" - << this->remote_node() << "': " << std::string(m_node_rd, 3); - this->handler()->on_connect_failure(this, str.str()); - m_socket.close(); - return; - } - - m_state = CS_WAIT_CHALLENGE; - - // recv challenge - m_node_rd += 3; - got_bytes -= 3; - if (got_bytes >= 2) - handle_read_challenge_header(boost::system::error_code(), 0); - else - boost::asio::async_read(m_socket, boost::asio::buffer(m_node_wr, (m_buf_node+1) - m_node_wr), - boost::asio::transfer_at_least(2), - boost::bind(&tcp_connection::handle_read_challenge_header, shared_from_this(), - boost::asio::placeholders::error, - boost::asio::placeholders::bytes_transferred)); -} - -template -void tcp_connection::handle_read_challenge_header( - const boost::system::error_code& err, size_t bytes_transferred) -{ - BOOST_ASSERT(m_state == CS_WAIT_CHALLENGE); - if (err) { - std::stringstream str; str << "Error reading auth challenge from node '" - << this->remote_node() << "': " << err.message(); - this->handler()->on_connect_failure(this, str.str()); - m_socket.close(); - return; - } - - m_node_wr += bytes_transferred; - m_expect_size = get16be(m_node_rd); - - if ((m_expect_size - 11) > (size_t)MAXNODELEN || m_expect_size > (size_t)((m_buf_node+1)-m_node_rd)) { - std::stringstream str; str << "Error in auth status challenge node length " - << this->remote_node() << " : " << m_expect_size; - this->handler()->on_connect_failure(this, str.str()); - m_socket.close(); - return; - } - - int need_bytes = m_expect_size - (m_node_wr - m_node_rd); - if (need_bytes > 0) { - boost::asio::async_read(m_socket, - boost::asio::buffer(m_node_wr, (m_buf_node+1) - m_node_wr), - boost::asio::transfer_at_least(need_bytes), - boost::bind(&tcp_connection::handle_read_challenge_body, shared_from_this(), - boost::asio::placeholders::error, - boost::asio::placeholders::bytes_transferred)); - } else { - handle_read_challenge_body(boost::system::error_code(), 0); - } -} - -template -void tcp_connection::handle_read_challenge_body( - const boost::system::error_code& err, size_t bytes_transferred) -{ - BOOST_ASSERT(m_state == CS_WAIT_CHALLENGE); - if (err) { - std::stringstream str; str << "Error reading auth challenge body from node '" - << this->remote_node() << "': " << err.message(); - this->handler()->on_connect_failure(this, str.str()); - m_socket.close(); - return; - } - - m_node_wr += bytes_transferred; - #ifndef BOOST_DISABLE_ASSERTS - int got_bytes = m_node_wr - m_node_rd; - #endif - BOOST_ASSERT(got_bytes >= (int)m_expect_size); - - char tag = get8(m_node_rd); - if (tag != 'n') { - std::stringstream str; str << "Error reading auth challenge tag '" - << this->remote_node() << "': " << tag; - this->handler()->on_connect_failure(this, str.str()); - m_socket.close(); - return; - } - - int version = get16be(m_node_rd); - int flags = get32be(m_node_rd); - m_remote_challenge = get32be(m_node_rd); - - if (this->handler()->verbose() >= VERBOSE_TRACE) { - std::stringstream s; - s << "<- got auth challenge (version=" << version - << ", flags=" << flags << ", remote_challenge=" << m_remote_challenge << ')'; - this->handler()->report_status(REPORT_INFO, s.str()); - } - - uint8_t our_digest[16]; - gen_digest(m_remote_challenge, this->m_cookie.c_str(), our_digest); - - // send challenge reply - int siz = 2 + 1 + 4 + 16; - char* w = m_buf_node; - put16be(w,siz - 2); - put8(w, 'r'); - put32be(w, m_our_challenge); - memcpy (w, our_digest, 16); - - if (this->handler()->verbose() >= VERBOSE_TRACE) { - std::stringstream s; - s << "-> sending challenge reply " << siz << " bytes:" - << to_binary_string(m_buf_node, siz); - this->handler()->report_status(REPORT_INFO, s.str()); - } - - m_state = CS_WAIT_WRITE_CHALLENGE_REPLY_DONE; - boost::asio::async_write(m_socket, boost::asio::buffer(m_buf_node, siz), - boost::bind(&tcp_connection::handle_write_challenge_reply, shared_from_this(), - boost::asio::placeholders::error)); -} - -template -void tcp_connection::handle_write_challenge_reply(const boost::system::error_code& err) -{ - BOOST_ASSERT(m_state == CS_WAIT_WRITE_CHALLENGE_REPLY_DONE); - if (err) { - std::stringstream str; str << "Error writing auth challenge reply to node '" - << this->remote_node() << "': " << err.message(); - this->handler()->on_connect_failure(this, str.str()); - m_socket.close(); - return; - } - m_state = CS_WAIT_CHALLENGE_ACK; - boost::asio::async_read(m_socket, boost::asio::buffer(m_buf_node, sizeof(m_buf_node)), - boost::asio::transfer_at_least(2), - boost::bind(&tcp_connection::handle_read_challenge_ack_header, shared_from_this(), - boost::asio::placeholders::error, - boost::asio::placeholders::bytes_transferred)); -} - -template -void tcp_connection::handle_read_challenge_ack_header( - const boost::system::error_code& err, size_t bytes_transferred) -{ - BOOST_ASSERT(m_state == CS_WAIT_CHALLENGE_ACK); - if (err) { - std::stringstream str; str << "Error reading auth challenge ack from node '" - << this->remote_node() << "': " - << (err == boost::asio::error::eof ? "Possibly bad cookie?" : err.message()); - this->handler()->on_connect_failure(this, str.str()); - m_socket.close(); - return; - } - - m_node_rd = m_buf_node; - m_expect_size = get16be(m_node_rd); - - if (m_expect_size > sizeof(m_buf_node)-2) { - std::stringstream str; str << "Error in auth status challenge ack length " - << this->remote_node() << " : " << m_expect_size; - this->handler()->on_connect_failure(this, str.str()); - m_socket.close(); - return; - } - - m_node_wr = m_buf_node + bytes_transferred; - - int need_bytes = m_expect_size - (m_node_wr - m_node_rd); - if (need_bytes > 0) { - boost::asio::async_read(m_socket, - boost::asio::buffer(m_node_wr, (m_buf_node+1)-m_node_wr), - boost::asio::transfer_at_least(need_bytes), - boost::bind(&tcp_connection::handle_read_challenge_ack_body, - shared_from_this(), - boost::asio::placeholders::error, - boost::asio::placeholders::bytes_transferred)); - } else { - handle_read_challenge_ack_body(boost::system::error_code(), 0); - } -} - -template -void tcp_connection::handle_read_challenge_ack_body( - const boost::system::error_code& err, size_t bytes_transferred) -{ - BOOST_ASSERT(m_state == CS_WAIT_CHALLENGE_ACK); - if (err) { - std::stringstream str; str << "Error reading auth challenge ack body from node '" - << this->remote_node() << "': " << err.message(); - this->handler()->on_connect_failure(this, str.str()); - m_socket.close(); - return; - } - - m_node_wr += bytes_transferred; - #ifndef BOOST_DISABLE_ASSERTS - int got_bytes = m_node_wr - m_node_rd; - #endif - BOOST_ASSERT(got_bytes >= (int)m_expect_size); - - char tag = get8(m_node_rd); - if (this->handler()->verbose() >= VERBOSE_TRACE) { - std::stringstream s; - s << "<- got auth challenge ack (tag=" << tag << "): " - << to_binary_string(m_node_rd, m_expect_size-1); - this->handler()->report_status(REPORT_INFO, s.str()); - } - - if (tag != 'a') { - std::stringstream str; str << "Error reading auth challenge ack body tag '" - << this->remote_node() << "': " << tag; - this->handler()->on_connect_failure(this, str.str()); - m_socket.close(); - return; - } - - char her_digest[16], expected_digest[16]; - memcpy(her_digest, m_node_rd, 16); - gen_digest(m_our_challenge, this->m_cookie.c_str(), (uint8_t*)expected_digest); - if (memcmp(her_digest, expected_digest, 16) != 0) { - std::stringstream str; str << "Authentication failure at node '" - << this->remote_node() << '!'; - this->handler()->on_connect_failure(this, str.str()); - m_socket.close(); - return; - } - - m_socket.set_option(boost::asio::ip::tcp::no_delay(true)); - m_socket.set_option(boost::asio::socket_base::keep_alive(true)); - - m_state = CS_CONNECTED; - - this->start(); -} - -template -uint32_t tcp_connection::gen_challenge(void) -{ -#if defined(__WIN32__) - struct { - SYSTEMTIME tv; - DWORD cpu; - int pid; - } s; - GetSystemTime(&s.tv); - s.cpu = GetTickCount(); - s.pid = getpid(); - -#else - struct { - struct timeval tv; - clock_t cpu; - pid_t pid; - u_long hid; - uid_t uid; - gid_t gid; - struct utsname name; - } s; - - gettimeofday(&s.tv, 0); - uname(&s.name); - s.cpu = clock(); - s.pid = getpid(); - s.hid = gethostid(); - s.uid = getuid(); - s.gid = getgid(); -#endif - return md_32((char*) &s, sizeof(s)); -} - -template -uint32_t tcp_connection:: -md_32(char* string, int length) -{ - BOOST_STATIC_ASSERT(MD5_DIGEST_LENGTH == 16); - union { - char c[MD5_DIGEST_LENGTH]; - unsigned x[4]; - } digest; - MD5((unsigned char *) string, length, (unsigned char *) digest.c); - return (digest.x[0] ^ digest.x[1] ^ digest.x[2] ^ digest.x[3]); - return (digest.x[0] ^ digest.x[1] ^ digest.x[2] ^ digest.x[3]); -} - -template -void tcp_connection:: -gen_digest(unsigned challenge, const char cookie[], uint8_t digest[16]) -{ - MD5_CTX c; - char chbuf[21]; - snprintf(chbuf, sizeof(chbuf), "%u", challenge); - MD5_Init(&c); - MD5_Update(&c, (unsigned char *) cookie, (uint32_t) strlen(cookie)); - MD5_Update(&c, (unsigned char *) chbuf, (uint32_t) strlen(chbuf)); - MD5_Final(digest, &c); -} - -} // namespace connect -} // namespace EIXX_NAMESPACE - -#endif // _EIXX_CONNECTION_TCP_IPP_ - diff --git a/include/eixx/connect/transport_otp_connection_uds.hpp b/include/eixx/connect/transport_otp_connection_uds.hpp index bd14048..621eeda 100644 --- a/include/eixx/connect/transport_otp_connection_uds.hpp +++ b/include/eixx/connect/transport_otp_connection_uds.hpp @@ -12,23 +12,19 @@ /* ***** BEGIN LICENSE BLOCK ***** -This file is part of the eixx (Erlang C++ Interface) library. +Copyright 2010 Serge Aleynikov -Copyright (c) 2010 Serge Aleynikov +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. + http://www.apache.org/licenses/LICENSE-2.0 -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. ***** END LICENSE BLOCK ***** */ @@ -38,7 +34,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include -namespace EIXX_NAMESPACE { +namespace eixx { namespace connect { //---------------------------------------------------------------------------- @@ -63,8 +59,12 @@ class uds_connection boost::asio::local::stream_protocol::socket& socket() { return m_socket; } void start() { +#if BOOST_VERSION >= 104700 + m_socket.non_blocking(true); +#else boost::asio::local::stream_protocol::socket::non_blocking_io nb(true); m_socket.io_control(nb); +#endif base_t::start(); } @@ -74,28 +74,37 @@ class uds_connection return m_uds_filename; } - int native_socket() { return m_socket.native(); } + int native_socket() { +#if BOOST_VERSION >= 104700 + return m_socket.native_handle(); +#else + return m_socket.native(); +#endif + } + + uint64_t remote_flags() const { return 0; } private: /// Socket for the connection. boost::asio::local::stream_protocol::socket m_socket; std::string m_uds_filename; - void connect(const std::string& a_this_node, - const std::string& a_remote_node, const std::string& a_cookie) - throw(std::runtime_error) + /// @throws std::runtime_error + void connect(uint32_t a_this_creation, atom a_this_node, atom a_remote_nodename, atom a_cookie) { - base_t::connect(a_this_node, a_remote_node, a_cookie); + base_t::connect(a_this_creation, a_this_node, a_remote_nodename, a_cookie); boost::system::error_code err; - boost::asio::local::stream_protocol::endpoint endpoint(a_remote_node); + boost::asio::local::stream_protocol::endpoint endpoint(a_remote_nodename.to_string()); m_socket.connect(endpoint, err); if (err) THROW_RUNTIME_ERROR("Error connecting to: " << m_uds_filename << ':' << err.message()); - size_t n = a_remote_node.find_last_of('/'); - this->m_remote_node = (n != std::string::npos) ? a_remote_node.substr(n+1) : a_remote_node; - m_uds_filename = a_remote_node; + auto s = a_remote_nodename.to_string(); + auto n = s.find_last_of('/'); + if (n != std::string::npos) s.erase(n); + this->m_remote_nodename = atom(s); + m_uds_filename = a_remote_nodename.to_string(); this->start(); } @@ -106,6 +115,6 @@ class uds_connection //------------------------------------------------------------------------------ } // namespace connect -} // namespace EIXX_NAMESPACE +} // namespace eixx #endif // _EIXX_TRANSPORT_OTP_CONNECTION_UDS_HPP_ diff --git a/include/eixx/connect/verbose.hpp b/include/eixx/connect/verbose.hpp index 489c426..a6dfa5b 100644 --- a/include/eixx/connect/verbose.hpp +++ b/include/eixx/connect/verbose.hpp @@ -11,7 +11,7 @@ #ifndef _EIXX_VERBOSE_HPP_ #define _EIXX_VERBOSE_HPP_ -namespace EIXX_NAMESPACE { +namespace eixx { // otp_node.on_status reporting level enum report_level { @@ -34,7 +34,7 @@ enum verbose_type { }; } // namespace connect -} // namespace EIXX_NAMESPACE +} // namespace eixx #endif // _EIXX_VERBOSE_HPP_ diff --git a/include/eixx/eixx.hpp b/include/eixx/eixx.hpp index 4b8c64f..54bf6d5 100644 --- a/include/eixx/eixx.hpp +++ b/include/eixx/eixx.hpp @@ -14,23 +14,19 @@ /* ***** BEGIN LICENSE BLOCK ***** -This file is part of the eixx (Erlang C++ Interface) Library. +Copyright 2010 Serge Aleynikov -Copyright (c) 2010 Serge Aleynikov +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. + http://www.apache.org/licenses/LICENSE-2.0 -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. ***** END LICENSE BLOCK ***** */ diff --git a/include/eixx/eterm.hpp b/include/eixx/eterm.hpp index 4bd1930..55113fe 100644 --- a/include/eixx/eterm.hpp +++ b/include/eixx/eterm.hpp @@ -9,23 +9,19 @@ /* ***** BEGIN LICENSE BLOCK ***** -This file is part of the eixx (Erlang C++ Interface) Library. +Copyright 2010 Serge Aleynikov -Copyright (C) 2010 Serge Aleynikov +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. + http://www.apache.org/licenses/LICENSE-2.0 -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. ***** END LICENSE BLOCK ***** */ @@ -35,10 +31,14 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include #include -namespace EIXX_NAMESPACE { +#define EIXX_DECL_ATOM(Atom) static const eixx::atom am_##Atom(#Atom) +#define EIXX_DECL_ATOM_VAL(Atom, Val) static const eixx::atom am_##Atom(Val) +#define EIXX_DECL_ATOM_VAR(Name, Atom) static const eixx::atom Name(Atom) + +namespace eixx { typedef marshal::eterm eterm; -typedef marshal::atom atom; +typedef marshal::atom atom; typedef marshal::string string; typedef marshal::binary binary; typedef marshal::epid epid; @@ -46,26 +46,28 @@ typedef marshal::port port; typedef marshal::ref ref; typedef marshal::tuple tuple; typedef marshal::list list; +typedef marshal::map map; typedef marshal::trace trace; -typedef marshal::var var; +typedef marshal::var var; typedef marshal::varbind varbind; typedef marshal::eterm_pattern_matcher eterm_pattern_matcher; typedef marshal::eterm_pattern_action eterm_pattern_action; namespace detail { - BOOST_STATIC_ASSERT(sizeof(eterm) == 2*sizeof(void*)); - BOOST_STATIC_ASSERT(sizeof(atom) == sizeof(void*)); - BOOST_STATIC_ASSERT(sizeof(string) == sizeof(void*)); - BOOST_STATIC_ASSERT(sizeof(binary) == sizeof(void*)); - BOOST_STATIC_ASSERT(sizeof(epid) == sizeof(void*)); - BOOST_STATIC_ASSERT(sizeof(port) == sizeof(void*)); - BOOST_STATIC_ASSERT(sizeof(ref) == sizeof(void*)); - BOOST_STATIC_ASSERT(sizeof(tuple) == sizeof(void*)); - BOOST_STATIC_ASSERT(sizeof(list) == sizeof(void*)); - BOOST_STATIC_ASSERT(sizeof(trace) == sizeof(void*)); - BOOST_STATIC_ASSERT(sizeof(var) == sizeof(void*)); + BOOST_STATIC_ASSERT(sizeof(eterm) == (alignof(uint64_t) > sizeof(int) ? alignof(uint64_t) : sizeof(int)) + sizeof(uint64_t)); + BOOST_STATIC_ASSERT(sizeof(atom) <= sizeof(uint64_t)); + BOOST_STATIC_ASSERT(sizeof(string) <= sizeof(uint64_t)); + BOOST_STATIC_ASSERT(sizeof(binary) <= sizeof(uint64_t)); + BOOST_STATIC_ASSERT(sizeof(epid) <= sizeof(uint64_t)); + BOOST_STATIC_ASSERT(sizeof(port) <= sizeof(uint64_t)); + BOOST_STATIC_ASSERT(sizeof(ref) <= sizeof(uint64_t)); + BOOST_STATIC_ASSERT(sizeof(tuple) <= sizeof(uint64_t)); + BOOST_STATIC_ASSERT(sizeof(list) <= sizeof(uint64_t)); + BOOST_STATIC_ASSERT(sizeof(map) <= sizeof(uint64_t)); + BOOST_STATIC_ASSERT(sizeof(trace) <= sizeof(uint64_t)); + BOOST_STATIC_ASSERT(sizeof(var) == sizeof(uint64_t)); } // namespace detail -} // namespace EIXX_NAMESPACE +} // namespace eixx #endif diff --git a/include/eixx/eterm_exception.hpp b/include/eixx/eterm_exception.hpp index 3ad9eda..ecf5bc9 100644 --- a/include/eixx/eterm_exception.hpp +++ b/include/eixx/eterm_exception.hpp @@ -9,23 +9,19 @@ /* ***** BEGIN LICENSE BLOCK ***** -This file is part of the eixx (Erlang C++ Interface) library. +Copyright 2010 Serge Aleynikov -Copyright (c) 2010 Serge Aleynikov +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. + http://www.apache.org/licenses/LICENSE-2.0 -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. ***** END LICENSE BLOCK ***** */ @@ -37,7 +33,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include #include -namespace EIXX_NAMESPACE { +namespace eixx { /** * Base class for the other eixx erlang exception classes. @@ -47,10 +43,11 @@ class eterm_exception: public std::exception { eterm_exception() {} eterm_exception(const std::string& msg): m_msg(msg) {} - template - eterm_exception(const std::string& msg, Arg a) { + template + eterm_exception(const std::string& msg, Arg&&... a) { std::ostringstream str; - str << msg << ": " << a; + str << msg << ": "; + (str << ... << a); // Fold expression m_msg = str.str(); } @@ -63,11 +60,16 @@ class eterm_exception: public std::exception { std::string m_msg; }; -/** - * Exception for invalid terms - */ -class err_invalid_term: public eterm_exception { -public: +/// Exception for invalid atoms +struct err_atom_not_found: public eterm_exception { + err_atom_not_found(std::string const& atom) + : eterm_exception("Atom '" + atom + "' not found") + {} + err_atom_not_found() : eterm_exception("Atom not found") {} +}; + +/// Exception for invalid terms +struct err_invalid_term: public eterm_exception { err_invalid_term(const std::string &msg) : eterm_exception(msg) {} }; @@ -128,46 +130,69 @@ class err_unbound_variable: public eterm_exception { class err_format_exception: public eterm_exception { const char* m_pos; const char* m_start; + std::string m_what; public: - err_format_exception(const std::string &msg, const char* pos) + err_format_exception( + const std::string& msg, const char* pos, const char* start = nullptr) : eterm_exception(msg) , m_pos(pos) - {} - - const char* what() const throw() { + , m_start(start) + { std::stringstream s; s << m_msg << " (" << (m_pos - m_start) << ")."; - return s.str().c_str(); + m_what = s.str(); } - const char* pos() const { return m_pos; } - void start(const char* a_start) { m_start = a_start; } + + const char* what() const throw() { return m_what.c_str(); } + const char* pos() const { return m_pos; } + void start(const char* a_start) { m_start = a_start; } }; /** * Exception while encoding */ class err_encode_exception: public eterm_exception { - int m_code; + int m_code; + long m_value; + std::string m_what; public: - err_encode_exception(const std::string &msg, int code=0) + err_encode_exception(const std::string &msg, int code=0, long value=0) : eterm_exception(msg) , m_code(code) - {} - - const char* what() const throw() { - std::stringstream s; s << m_msg << " (" << m_code << ")."; - return s.str().c_str(); + , m_value(value) + { + std::stringstream s; s << m_msg; + if (value != 0) s << " (" << m_value << ")"; + if (code > -1) s << " at (" << m_code << ")"; + m_what = s.str(); } - int code() const { return m_code; } + + const char* what() const throw() { return m_what.c_str(); } + int code() const { return m_code; } + long value() const { return m_value; } }; /** * Exception while decoding */ -class err_decode_exception: public err_encode_exception { +class err_decode_exception: public eterm_exception { + uintptr_t m_pos; + long m_value; + std::string m_what; public: - err_decode_exception(const std::string &msg, int code=0) - : err_encode_exception(msg, code) - {} + err_decode_exception(const std::string &msg, uintptr_t pos=0, long value=0) + : eterm_exception(msg) + , m_pos(pos) + , m_value(value) + { + std::stringstream s; s << m_msg; + if (value != 0) s << " (" << m_value << ")"; + s << " at (" << m_pos << ")"; + m_what = s.str(); + } + + const char* what() const throw() { return m_what.c_str(); } + uintptr_t pos() const { return m_pos; } + long value() const { return m_value; } }; class err_empty_list: public eterm_exception { @@ -197,6 +222,6 @@ class err_no_process: public err_bad_argument { err_no_process(const std::string &msg, T arg): err_bad_argument(msg, arg) {} }; -} // namespace EIXX_NAMESPACE +} // namespace eixx #endif // _EIXX_EXCEPTION_HPP_ diff --git a/include/eixx/marshal/alloc_base.hpp b/include/eixx/marshal/alloc_base.hpp index 1c5867d..1577a60 100644 --- a/include/eixx/marshal/alloc_base.hpp +++ b/include/eixx/marshal/alloc_base.hpp @@ -9,23 +9,19 @@ /* ***** BEGIN LICENSE BLOCK ***** -This file is part of the eixx (Erlang C++ Interface) library. +Copyright 2010 Serge Aleynikov -Copyright (C) 2010 Serge Aleynikov +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. + http://www.apache.org/licenses/LICENSE-2.0 -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. ***** END LICENSE BLOCK ***** */ @@ -36,13 +32,14 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include #include #include +#include -namespace EIXX_NAMESPACE { +namespace eixx { namespace marshal { template struct alloc_base_impl { - typedef typename Alloc::template rebind::other T_alloc_type; + using T_alloc_type = typename std::allocator_traits::template rebind_alloc; struct alloc_impl: public T_alloc_type { alloc_impl() : T_alloc_type() {} @@ -56,11 +53,11 @@ namespace marshal { /// empty base class optimization. template class alloc_base : private alloc_base_impl::alloc_impl { - typedef alloc_base_impl impl_t; - typedef typename impl_t::alloc_impl base_t; + using impl_t = alloc_base_impl; + using base_t = typename impl_t::alloc_impl; public: - typedef Alloc allocator_type; - typedef typename impl_t::T_alloc_type T_alloc_type; + using allocator_type = Alloc; + using T_alloc_type = typename impl_t::T_alloc_type; alloc_base() {} alloc_base(const Alloc& alloc) : base_t(alloc) {} @@ -78,19 +75,23 @@ namespace marshal { /// \brief Reference-counted blob of memory to store the object of type T. template class blob : private boost::noncopyable - , public Alloc::template rebind::other + , public std::allocator_traits::template rebind_alloc { - typedef typename Alloc::template rebind::other base_t; - typedef typename Alloc::template rebind >::other blob_alloc_t; + using base_t = typename std::allocator_traits::template rebind_alloc; + using blob_alloc_t = typename std::allocator_traits::template rebind_alloc>; - atomic m_rc; - const size_t m_size; - T* m_data; + atomic m_rc; + const size_t m_size; + T* m_data; ~blob() { - this->deallocate(m_data, m_size); + if (m_data) + this->deallocate(m_data, m_size); } + /// This is needed in order to allow blobs to be hosted inside + /// std::unique_ptr: + template friend struct std::default_delete; public: blob(const Alloc& a = Alloc()) : base_t(a), m_rc(1), m_size(0), m_data(NULL) @@ -129,7 +130,7 @@ namespace marshal { /// Increment internal reference count. void inc_rc() { ++m_rc; } /// Return internal reference count. Use for debugging only. - int use_count() const { return m_rc; } + uint32_t use_count() const { return m_rc; } const Alloc& get_allocator() const { return *reinterpret_cast(this); @@ -142,20 +143,20 @@ namespace marshal { /// This method overrides the new() operator for this class /// so that the blob memory is taken from the Alloc allocator. - static void* operator new(size_t sz) { + static void* operator new([[maybe_unused]] size_t sz) { BOOST_ASSERT(sz == sizeof(blob)); return get_blob_alloc().allocate(1); } /// This method overrides the new() operator for this class /// so that the blob memory is released to the Alloc allocator. - static void operator delete(void* p, size_t sz) { + static void operator delete(void* p, [[maybe_unused]] size_t sz) { BOOST_ASSERT(sz == sizeof(blob)); get_blob_alloc().deallocate(static_cast*>(p), 1); } }; } // namespace marshal -} // namespace EIXX_NAMESPACE +} // namespace eixx #endif // _EIXX_ETERM_BASE_HPP_ diff --git a/include/eixx/marshal/am.hpp b/include/eixx/marshal/am.hpp new file mode 100644 index 0000000..91a5d40 --- /dev/null +++ b/include/eixx/marshal/am.hpp @@ -0,0 +1,63 @@ +//---------------------------------------------------------------------------- +/// \file atom.hpp +//---------------------------------------------------------------------------- +/// \brief A class implementing an atom - enumerated string stored +/// stored in non-garbage collected hash table of fixed size. +//---------------------------------------------------------------------------- +// Copyright (c) 2010 Serge Aleynikov +// Created: 2010-09-20 +//---------------------------------------------------------------------------- +/* +***** BEGIN LICENSE BLOCK ***** + +Copyright 2010 Serge Aleynikov + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +***** END LICENSE BLOCK ***** +*/ +#pragma once + +#include + +namespace eixx { + + using marshal::atom; + + // Constant global atom values + + const atom am_ANY_ = atom("_"); + extern const atom am_badarg; + extern const atom am_badrpc; + extern const atom am_call; + extern const atom am_cast; + extern const atom am_erlang; + extern const atom am_error; + extern const atom am_false; + extern const atom am_format; + extern const atom am_gen_cast; + extern const atom am_io_lib; + extern const atom am_latin1; + extern const atom am_noconnection; + extern const atom am_noproc; + extern const atom am_normal; + extern const atom am_ok; + extern const atom am_request; + extern const atom am_rex; + extern const atom am_rpc; + extern const atom am_true; + extern const atom am_undefined; + extern const atom am_unsupported; + extern const atom am_user; + +} // namespace eixx diff --git a/include/eixx/marshal/atom.hpp b/include/eixx/marshal/atom.hpp index 2b31854..beee171 100644 --- a/include/eixx/marshal/atom.hpp +++ b/include/eixx/marshal/atom.hpp @@ -10,23 +10,19 @@ /* ***** BEGIN LICENSE BLOCK ***** -This file is part of the eixx (Erlang C++ Interface) Library. +Copyright 2010 Serge Aleynikov -Copyright (C) 2010 Serge Aleynikov +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. + http://www.apache.org/licenses/LICENSE-2.0 -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. ***** END LICENSE BLOCK ***** */ @@ -41,13 +37,14 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include #include #include +#include #include -namespace EIXX_NAMESPACE { +namespace eixx { namespace marshal { namespace detail { - namespace eid = EIXX_NAMESPACE::detail; + namespace eid = eixx::detail; using eid::lock_guard; /// Create an atom containing node name. @@ -59,203 +56,117 @@ namespace detail { } } - /// Non-garbage collected hash table for atoms. It stores strings - /// as atoms so that atoms can be quickly compared to with O(1) - /// complexity. The instance of this table is statically maintained - /// and its content is never cleared. The table contains a unique - /// list of strings represented as atoms added throughout the lifetime - /// of the application. - template - class atom_table { - public: - //typedef std::basic_string, Alloc> string_t; - typedef std::string string_t; - private: - // See http://www.azillionmonkeys.com/qed/hash.html - // Copyright 2004-2008 (c) by Paul Hsieh - struct hsieh_hash_fun { - static uint16_t get16bits(const char* d) { return *(const uint16_t *)d; } - - size_t operator()(const char* data) const { - int len = strlen(data); - uint32_t hash = len, tmp; - int rem; - - if (len <= 0 || data == NULL) return 0; - - rem = len & 3; - len >>= 2; - - /* Main loop */ - for (;len > 0; len--) { - hash += get16bits (data); - tmp = (get16bits (data+2) << 11) ^ hash; - hash = (hash << 16) ^ tmp; - data += 2*sizeof (uint16_t); - hash += hash >> 11; - } - - /* Handle end cases */ - switch (rem) { - case 3: hash += get16bits (data); - hash ^= hash << 16; - hash ^= data[sizeof (uint16_t)] << 18; - hash += hash >> 11; - break; - case 2: hash += get16bits (data); - hash ^= hash << 11; - hash += hash >> 17; - break; - case 1: hash += *data; - hash ^= hash << 10; - hash += hash >> 1; - } - - /* Force "avalanching" of final 127 bits */ - hash ^= hash << 3; - hash += hash >> 5; - hash ^= hash << 4; - hash += hash >> 17; - hash ^= hash << 25; - hash += hash >> 6; - - return hash; - } - }; - - static const size_t s_default_max_atoms = 1024*1024; - - int find_value(size_t bucket, const char* a_atom) { - typename char_int_hash_map::const_local_iterator - lit = m_index.begin(bucket), lend = m_index.end(bucket); - while(lit != lend && strcmp(a_atom, lit->first) != 0) ++lit; - return lit == lend ? -1 : lit->second; - } - public: - /// Returns the default atom table maximum size. The value can be - /// changed by setting the EI_ATOM_TABLE_SIZE environment variable. - static size_t default_size() { - const char* p = getenv("EI_ATOM_TABLE_SIZE"); - int n = p ? atoi(p) : s_default_max_atoms; - return n > 0 && n < 1024*1024*100 ? n : s_default_max_atoms; - } - - /// Returns the maximum number of atoms that can be stored in the atom table. - size_t capacity() const { return m_atoms.capacity(); } - - /// Returns the current number of atoms stored in the atom table. - size_t allocated() const { return m_atoms.size(); } - - explicit atom_table(size_t a_max_atoms = default_size()) - : m_index(a_max_atoms) { - m_atoms.reserve(a_max_atoms); - m_atoms.push_back(""); // The 0-th element is an empty atom (""). - m_index[""] = 0; - } - - ~atom_table() { - lock_guard guard(m_lock); - m_atoms.clear(); - m_index.clear(); - } - - /// Lookup an atom in the atom table by index. - const string_t& lookup(size_t n) const { return (*this)[n]; } - - /// Lookup an atom in the atom table by index. - const string_t& operator[] (size_t n) const { - BOOST_ASSERT(n < m_atoms.size()); - return m_atoms[n]; - } - - /// Lookup an atom in the atom table by name. If the atom is not - /// present in the atom table - add it. Return the index of the - /// atom in the atom table. - /// @throws std::runtime_error if atom table is full. - /// @throws err_bad_argument if atom size is longer than MAXATOMLEN - size_t lookup(const char* a_atom, size_t n) { return lookup(std::string(a_atom, n)); } - size_t lookup(const char* a_atom) { return lookup(std::string(a_atom)); } - size_t lookup(const std::string& a_atom) - throw(std::runtime_error, err_bad_argument) - { - if (a_atom.size() == 0) - return 0; - if (a_atom.size() > MAXATOMLEN) - throw err_bad_argument("Atom size is too long!"); - size_t bucket = m_index.bucket(a_atom.c_str()); - int n = find_value(bucket, a_atom.c_str()); - if (n >= 0) - return n; - - lock_guard guard(m_lock); - n = find_value(bucket, a_atom.c_str()); - if (n >= 0) - return n; - - n = m_atoms.size(); - if ((size_t)(n+1) == m_atoms.capacity()) - throw std::runtime_error("Atom hash table is full!"); - m_atoms.push_back(a_atom); - m_index[a_atom.c_str()] = n; - return n; - } - private: - std::vector m_atoms; - char_int_hash_map m_index; - Mutex m_lock; - }; } // namespace detail /** * Provides a representation of Erlang atoms. Atoms can be - * created from strings whose length is not more than - * MAXATOMLEN characters. + * created from UTF8 strings whose length is not more than + * MAXATOMLEN characters / codepoints. */ class atom { - size_t m_index; - - typedef detail::atom_table<>::string_t string_t; + uint32_t m_index; + atom(uint32_t idx) : m_index(idx) {} public: - static detail::atom_table<>& atom_table(); + inline static util::atom_table& atom_table() { + static util::atom_table s_atom_table; + return s_atom_table; + } + + /// Returns empty atom + inline static const atom null() { + static const atom null = atom(); + return null; + } /// Create an empty atom atom() : m_index(0) { - BOOST_STATIC_ASSERT(sizeof(atom) == sizeof(void*)); + static_assert(sizeof(atom) == 4, "Invalid atom size!"); } /// Create an atom from the given string. /// @param atom the string to create the atom from. - /// @throws std::runtime_error if atom table is full. - /// @throws err_bad_argument if atom size is longer than MAXATOMLEN + /// @throw std::runtime_error if atom table is full. + /// @throw err_bad_argument if atom length is longer than MAXATOMLEN atom(const char* s) - throw(std::runtime_error, err_bad_argument) - : m_index(atom_table().lookup(string_t(s))) {} + : m_index((uint32_t)atom_table().lookup(std::string(s))) {} /// @copydoc atom::atom - template + template atom(const char (&s)[N]) - throw(std::runtime_error, err_bad_argument) - : m_index(atom_table().lookup(string_t(s, N))) {} + : m_index((uint32_t)atom_table().lookup(std::string(s, N))) {} /// @copydoc atom::atom explicit atom(const std::string& s) - throw(std::runtime_error) - : m_index(atom_table().lookup(s)) + : m_index((uint32_t)atom_table().lookup(s)) {} + + /// Try to create an atom without throwing exceptions + /// NOTE: if the atom name is invalid or it doesn't exist and \a existing + /// is true, then an empty atom is returned. + static atom create(const std::string& s, bool existing) { + auto p = atom_table().try_lookup(s); + BOOST_ASSERT(p.second <= UINT32_MAX); + return p.first && existing ? atom((uint32_t)p.second) : atom(); + } + /// @copydoc atom::create + static atom create(const char* s, bool existing) { + return create(std::string(s), existing); + } + + /// Get atom length from a binary buffer encoded in + /// Erlang external binary format. + static long get_len(const char*& s, const uint8_t tag) { + switch (tag) { +#ifdef ERL_SMALL_ATOM_UTF8_EXT + case ERL_SMALL_ATOM_UTF8_EXT: return get8(s); +#endif +#ifdef ERL_ATOM_UTF8_EXT + case ERL_ATOM_UTF8_EXT: return get16be(s); +#endif +#ifdef ERL_SMALL_ATOM_EXT + case ERL_SMALL_ATOM_EXT: return get8(s); +#endif + case ERL_ATOM_EXT: return get16be(s); + default: return -1; + } + } + + /// @copydoc atom::atom + /// @param existing if true, check that the atom already exists, otherwise throw + /// err_atom_not_found + atom(const char* s, bool existing) + : atom(std::string(s), existing) {} + atom(const std::string& s, bool existing) + { + if (!existing) { + auto idx = atom_table().lookup(s); + BOOST_ASSERT(idx <= UINT32_MAX); + m_index = (uint32_t)idx; + return; + } + auto p = atom_table().try_lookup(s); + BOOST_ASSERT(p.second <= UINT32_MAX); + if (p.first) + m_index = (uint32_t)p.second; + else + throw err_atom_not_found(s); + } /// @copydoc atom::atom template explicit atom(const string& s) - throw(std::runtime_error) - : m_index(atom_table().lookup(string_t(s.c_str(), s.size()))) + : m_index((uint32_t)atom_table().lookup(std::string(s.c_str(), s.size()))) {} + /// @copydoc atom::atom + atom(const char* s, int n) : atom(s, static_cast(n)) {} + /// @copydoc atom::atom + atom(const char* s, long n) : atom(s, static_cast(n)) {} /// @copydoc atom::atom atom(const char* s, size_t n) - throw(std::runtime_error) - : m_index(atom_table().lookup(string_t(s, n))) + : m_index((uint32_t)atom_table().lookup(std::string(s, n))) {} /// Copy atom from another atom. This is a constant time @@ -264,30 +175,30 @@ class atom /// Decode an atom from a binary buffer encoded in /// Erlang external binary format. - atom(const char* a_buf, int& idx, size_t a_size) - throw (err_decode_exception, std::runtime_error) + atom(const char* a_buf, uintptr_t& idx, [[maybe_unused]] size_t a_size) { const char *s = a_buf + idx; const char *s0 = s; - if (get8(s) != ERL_ATOM_EXT) + const uint8_t tag = get8(s); + long len = get_len(s, tag); + if (len < 0) throw err_decode_exception("Error decoding atom", idx); - int len = get16be(s); - m_index = atom_table().lookup(string_t(s, len)); - idx += s + len - s0; + m_index = (uint32_t)atom_table().lookup(std::string(s, static_cast(len))); + idx += static_cast(s - s0) + static_cast(len); BOOST_ASSERT((size_t)idx <= a_size); } - const char* c_str() const { return atom_table()[m_index].c_str(); } - const string_t& to_string() const { return atom_table()[m_index]; } - size_t size() const { return atom_table()[m_index].size(); } - size_t length() const { return size(); } - bool empty() const { return m_index == 0; } + const char* c_str() const { return atom_table()[m_index].c_str(); } + const std::string& to_string() const { return atom_table()[m_index]; } + uint16_t size() const { return (uint16_t)atom_table()[m_index].size(); } + uint16_t length() const { return size(); } + bool empty() const { return m_index == 0; } /// Get atom's index in the atom table. - size_t index() const { return m_index; } + uint32_t index() const { return m_index; } void operator= (const atom& s) { m_index = s.m_index; } - void operator= (const std::string& s) { m_index = atom_table().lookup(s); } + void operator= (const std::string& s) { auto idx = atom_table().lookup(s); BOOST_ASSERT(idx <= UINT32_MAX); m_index = (uint32_t)idx; } bool operator== (const char* rhs) const { return strcmp(c_str(), rhs) == 0; } bool operator== (const atom& rhs) const { return m_index == rhs.m_index; } bool operator!= (const char* rhs) const { return !(*this == rhs); } @@ -304,23 +215,31 @@ class atom /// Get the size of a buffer needed to encode this atom in /// the external binary format. - size_t encode_size() const { return 3 + length(); } + size_t encode_size() const { + const size_t sz = size(); + return (sz > 255 ? 3 : 2) + sz; + } /// Encode the atom in external binary format. /// @param buf is the buffer space to encode the atom to. /// @param idx is the offset in the \a buf where to begin writing. /// @param size is the size of \a buf. - void encode(char* buf, int& idx, size_t size) const { + void encode(char* buf, uintptr_t& idx, [[maybe_unused]] size_t a_size) const { char* s = buf + idx; char* s0 = s; - const int len = std::min((size_t)MAXATOMLEN, length()); - /* This function is documented to truncate at MAXATOMLEN (256) */ - put8(s,ERL_ATOM_EXT); - put16be(s,len); - memmove(s,c_str(),len); /* unterminated string */ + const uint16_t len = std::min((uint16_t)MAXATOMLEN_UTF8, size()); + /* This function is documented to truncate at MAXATOMLEN_UTF8 (1021) */ + if (len > UINT8_MAX) { + put8(s, ERL_ATOM_UTF8_EXT); + put16be(s, len); + } else { + put8(s, ERL_SMALL_ATOM_UTF8_EXT); + put8(s, (uint8_t)len); + } + memmove(s, c_str(), len); /* unterminated string */ s += len; - idx += s-s0; - BOOST_ASSERT((size_t)idx <= size); + idx += static_cast(s - s0); + BOOST_ASSERT((size_t)idx <= a_size); } /// Write the atom to the \a out stream. @@ -337,22 +256,22 @@ class atom /// @param s is the string representation of the node name that must be /// in the form: \c Alivename@Hostname. /// @return atom representing node name -/// @throws std::runtime_error if atom table is full. -/// @throws err_bad_argument if atom size is longer than MAXNODELEN +/// @throw std::runtime_error if atom table is full. +/// @throw err_bad_argument if atom size is longer than MAXNODELEN inline atom make_node_name(const std::string& s) - throw(std::runtime_error, err_bad_argument) { if (!s.find('@')) throw err_bad_argument("Invalid node name", s); detail::check_node_length(s.size()); return atom(s); } + } // namespace marshal -} // namespace EIXX_NAMESPACE +} // namespace eixx namespace std { - inline ostream& operator<< (ostream& out, const EIXX_NAMESPACE::marshal::atom& s) { + inline ostream& operator<< (ostream& out, const eixx::marshal::atom& s) { return s.dump(out); } diff --git a/include/eixx/marshal/binary.hpp b/include/eixx/marshal/binary.hpp index 06bea45..5150140 100644 --- a/include/eixx/marshal/binary.hpp +++ b/include/eixx/marshal/binary.hpp @@ -10,23 +10,19 @@ /* ***** BEGIN LICENSE BLOCK ***** -This file is part of the eixx (Erlang C++ Interface) Library. +Copyright 2010 Serge Aleynikov -Copyright (C) 2010 Serge Aleynikov +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. + http://www.apache.org/licenses/LICENSE-2.0 -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. ***** END LICENSE BLOCK ***** */ @@ -41,7 +37,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include #include -namespace EIXX_NAMESPACE { +namespace eixx { namespace marshal { template @@ -49,10 +45,16 @@ class binary { blob* m_blob; - void decode(const char* buf, int& idx, size_t size) throw(err_decode_exception); + void decode(const char* buf, uintptr_t& idx, size_t size); - binary() {} public: + binary() : m_blob(nullptr) {} + + /// Create a binary from string + explicit binary(const std::string& a_bin, const Alloc& a_alloc = Alloc()) + : binary(a_bin.c_str(), a_bin.size(), a_alloc) + {} + /** * Create a binary from the given data. * Data is shared between all cloned binaries by using reference counting. @@ -60,15 +62,24 @@ class binary * @param size binary size in bytes * @param a_alloc is the allocator to use **/ - binary(const char* data, size_t size, const Alloc& a_alloc = Alloc()) - : m_blob(new blob(size, a_alloc)) { + binary(const char* data, size_t size, const Alloc& a_alloc = Alloc()) { + if (size == 0) { + m_blob = nullptr; + return; + } + m_blob = new blob(size, a_alloc); memcpy(m_blob->data(), data, size); } binary(const binary& rhs) : m_blob(rhs.m_blob) { - m_blob->inc_rc(); + if (m_blob) m_blob->inc_rc(); } + binary(binary&& rhs) : m_blob(rhs.m_blob) { rhs.m_blob = nullptr; } + + binary(std::initializer_list bytes, const Alloc& alloc = Alloc()) + : binary(reinterpret_cast(bytes.begin()), bytes.size(), alloc) {} + /** * Construct the object by decoding it from a binary * encoded buffer and using custom allocator. @@ -76,21 +87,37 @@ class binary * @param buf is the buffer containing Erlang external binary format. * @param idx is the current offset in the buf buffer. * @param size is the size of \a buf buffer. + * @throw err_decode_exception */ - binary(const char* buf, int& idx, size_t size, const Alloc& a_alloc = Alloc()) - throw(err_decode_exception); + binary(const char* buf, uintptr_t& idx, size_t size, const Alloc& a_alloc = Alloc()); /** Get the size of the data (in bytes) */ - size_t size() const { return m_blob->size(); } + size_t size() const { return m_blob ? m_blob->size() : 0; } /** Get the data's binary buffer */ - char* data() const { return m_blob->data(); } + const char* data() const { return m_blob ? m_blob->data() : ""; } + + binary& operator= (const binary& rhs) { + if (this != &rhs) { + m_blob = rhs.m_blob; + if (m_blob) m_blob->inc_rc(); + } + return *this; + } + + binary& operator= (binary&& rhs) { + if (this != &rhs) { + m_blob = rhs.m_blob; + rhs.m_blob = nullptr; + } + return *this; + } bool operator== (const binary& rhs) const { return size() == rhs.size() && (size() == 0 || memcmp(data(), rhs.data(), size()) == 0); } - bool operator< (const binary& rhs) { + bool operator< (const binary& rhs) const { if (size() < rhs.size()) return true; if (size() > rhs.size()) return false; if (size() == 0) return false; @@ -101,12 +128,12 @@ class binary } /** Encode binary to a flat buffer. */ - void encode(char* buf, int& idx, size_t size) const; + void encode(char* buf, uintptr_t& idx, size_t size) const; /** Size of binary buffer needed to hold encoded binary. */ size_t encode_size() const { return 5 + size(); } - std::ostream& dump(std::ostream& out, const varbind* binding=NULL) const { + std::ostream& dump(std::ostream& out, const varbind* =NULL) const { bool printable = size() > 1; for (const char* p = data(), *end = data() + size(); printable && p != end; ++p) if (*p < ' ' || *p > '~') @@ -114,21 +141,21 @@ class binary if (printable) return out << "<<" << string(data(), size()) << ">>"; else - return EIXX_NAMESPACE::to_binary_string(out, data(), size()); + return eixx::to_binary_string(out, data(), size()); } }; } // namespace marshal -} // namespace EIXX_NAMESPACE +} // namespace eixx namespace std { template - ostream& operator<< (ostream& out, const EIXX_NAMESPACE::marshal::binary& a) { + ostream& operator<< (ostream& out, const eixx::marshal::binary& a) { return a.dump(out); } } // namespace std -#include +#include #endif // _IMPL_BINARY_HPP_ diff --git a/include/eixx/marshal/binary.hxx b/include/eixx/marshal/binary.hxx new file mode 100644 index 0000000..1a1a0e6 --- /dev/null +++ b/include/eixx/marshal/binary.hxx @@ -0,0 +1,72 @@ +//---------------------------------------------------------------------------- +/// \file binary.hxx +//---------------------------------------------------------------------------- +/// \brief Implementation of binary class member functions. +//---------------------------------------------------------------------------- +// Copyright (c) 2010 Serge Aleynikov +// Created: 2010-09-20 +//---------------------------------------------------------------------------- +/* +***** BEGIN LICENSE BLOCK ***** + +Copyright 2010 Serge Aleynikov + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +***** END LICENSE BLOCK ***** +*/ + +#include +#include +#include + +namespace eixx { +namespace marshal { + +template +binary::binary(const char* buf, uintptr_t& idx, [[maybe_unused]] size_t size, const Alloc& a_alloc) +{ + const char* s = buf + idx; + const char* s0 = s; + uint8_t tag = get8(s); + + if (tag != ERL_BINARY_EXT) + throw err_decode_exception("Error decoding binary's type", idx, tag); + + uint32_t sz = get32be(s); + m_blob = new blob(sz, a_alloc); + memcpy(m_blob->data(),s,sz); + + idx += static_cast(s - s0) + sz; + BOOST_ASSERT((size_t)idx <= size); +} + +template +void binary::encode(char* buf, uintptr_t& idx, [[maybe_unused]] size_t size) const +{ + char* s = buf + idx; + char* s0 = s; + put8(s,ERL_BINARY_EXT); + size_t sz = this->size(); + if (sz > UINT32_MAX) + throw err_encode_exception("BINARY_EXT length exceeds maximum"); + uint32_t len = (uint32_t)sz; + put32be(s, len); + memmove(s, this->data(), len); + s += len; + idx += static_cast(s - s0); + BOOST_ASSERT((size_t)idx <= size); +} + +} // namespace marshal +} // namespace eixx diff --git a/include/eixx/marshal/binary.ipp b/include/eixx/marshal/binary.ipp deleted file mode 100644 index ce521e9..0000000 --- a/include/eixx/marshal/binary.ipp +++ /dev/null @@ -1,73 +0,0 @@ -//---------------------------------------------------------------------------- -/// \file binary.ipp -//---------------------------------------------------------------------------- -/// \brief Implementation of binary class member functions. -//---------------------------------------------------------------------------- -// Copyright (c) 2010 Serge Aleynikov -// Created: 2010-09-20 -//---------------------------------------------------------------------------- -/* -***** BEGIN LICENSE BLOCK ***** - -This file is part of the eixx (Erlang C++ Interface) Library. - -Copyright (C) 2010 Serge Aleynikov - -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -***** END LICENSE BLOCK ***** -*/ - -#include -#include -#include - -namespace EIXX_NAMESPACE { -namespace marshal { - -template -binary::binary(const char* buf, int& idx, size_t size, const Alloc& a_alloc) - throw (err_decode_exception) -{ - const char* s = buf + idx; - const char* s0 = s; - - if (get8(s) != ERL_BINARY_EXT) - throw err_decode_exception("Error decoding binary", idx); - - size_t sz = get32be(s); - m_blob = new blob(sz, a_alloc); - ::memcpy(data(),s,sz); - - idx += s + sz - s0; - BOOST_ASSERT((size_t)idx <= size); -} - -template -void binary::encode(char* buf, int& idx, size_t size) const -{ - char* s = buf + idx; - char* s0 = s; - put8(s,ERL_BINARY_EXT); - size_t n = this->size(); - put32be(s, n); - memmove(s, this->data(), n); - s += n; - idx += s-s0; - BOOST_ASSERT((size_t)idx <= size); -} - -} // namespace marshal -} // namespace EIXX_NAMESPACE diff --git a/include/eixx/marshal/config.hpp b/include/eixx/marshal/config.hpp new file mode 100644 index 0000000..b0aead7 --- /dev/null +++ b/include/eixx/marshal/config.hpp @@ -0,0 +1,44 @@ +//---------------------------------------------------------------------------- +/// \file config.hpp +//---------------------------------------------------------------------------- +/// \brief A class encapsulation static configuration options +//---------------------------------------------------------------------------- +// Copyright (c) 2021 Serge Aleynikov +// Created: 2021-08-25 +//---------------------------------------------------------------------------- +/* +***** BEGIN LICENSE BLOCK ***** + +Copyright 2010 Serge Aleynikov + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +***** END LICENSE BLOCK ***** +*/ + +#pragma once + +namespace eixx { +namespace marshal { + +/// Static configuration +class config { + static bool s_display_creation; +public: + /// When true - include 'Creation' in printing to string/stream + static bool display_creation() { return s_display_creation; } + static void display_creation(bool v) { s_display_creation = v; } +}; + +} // namespace marshal +} // namespace eixx diff --git a/include/eixx/marshal/defaults.hpp b/include/eixx/marshal/defaults.hpp index ab4e249..ca10160 100644 --- a/include/eixx/marshal/defaults.hpp +++ b/include/eixx/marshal/defaults.hpp @@ -9,49 +9,35 @@ /* ***** BEGIN LICENSE BLOCK ***** -This file is part of the eixx (Erlang C++ Interface) Library. +Copyright 2010 Serge Aleynikov -Copyright (C) 2010 Serge Aleynikov +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. + http://www.apache.org/licenses/LICENSE-2.0 -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. ***** END LICENSE BLOCK ***** */ #ifndef _EIXX_DEFAULTS_HPP #define _EIXX_DEFAULTS_HPP -#ifndef EIXX_NAMESPACE -#define EIXX_NAMESPACE eixx -#endif - -// Branch prediction optimization (see http://lwn.net/Articles/255364/) -#ifndef NO_HINT_BRANCH_PREDICTION -# define unlikely(expr) __builtin_expect(!!(expr), 0) -# define likely(expr) __builtin_expect(!!(expr), 1) -#else -# define unlikely(expr) (expr) -# define likely(expr) (expr) -#endif +#include +#include -namespace EIXX_NAMESPACE { +namespace eixx { namespace marshal { // Forward declarations - template struct eterm; - template struct tuple; - template struct list; + template class eterm; + template class tuple; + template class list; namespace marshal { template struct visit_eterm_stringify; @@ -72,24 +58,81 @@ namespace EIXX_NAMESPACE { , LONG = 1 , DOUBLE = 2 , BOOL = 3 - // ATOM is the first compound item having a constructor , ATOM = 4 - , STRING = 5 - , BINARY = 6 - , PID = 7 - , PORT = 8 - , REF = 9 - , VAR = 10 + , VAR = 5 + // STRING is the first compound item that requires destruction + , STRING = 6 + , BINARY = 7 + , PID = 8 + , PORT = 9 + , REF = 10 , TUPLE = 11 , LIST = 12 - , TRACE = 13 - , MAX_ETERM_TYPE = 13 + , MAP = 13 + , TRACE = 14 + , MAX_ETERM_TYPE = 14 }; /// Returns string representation of type \a a_type. const char* type_to_string(eterm_type a_type); -} // namespace EIXX_NAMESPACE + /// Converts \a a_type to string + /// @param a_type is the type to convert + /// @param a_prefix if true, the value is prepended with "::" + /// + /// Example: printf("%s\n", type_to_type_string(eterm_type::BOOL, true); + /// Outputs: ::bool() + const char* type_to_type_string(eterm_type a_type, bool a_prefix=false); + + /// Converts a string to eterm type (e.g. "binary" -> eterm_type::BINARY) + eterm_type type_string_to_type(const char* s, size_t n); + + /// Converts a string \a s to eterm type (e.g. "binary" -> eterm_type::BINARY) + inline eterm_type type_string_to_type(const char* s) { + return type_string_to_type(s, strlen(s)); + } + + inline const char* type_to_string(eterm_type a_type) { + switch (a_type) { + case LONG : return "LONG"; + case DOUBLE: return "DOUBLE"; + case BOOL : return "BOOL"; + case ATOM : return "ATOM"; + case STRING: return "STRING"; + case BINARY: return "BINARY"; + case PID : return "PID"; + case PORT : return "PORT"; + case REF : return "REF"; + case VAR : return "VAR"; + case TUPLE : return "TUPLE"; + case LIST : return "LIST"; + case MAP : return "MAP"; + case TRACE : return "TRACE"; + default : return "UNDEFINED"; + } + } + + inline const char* type_to_type_string(eterm_type a_type, bool a_prefix) { + switch (a_type) { + case LONG : return a_prefix ? "::int()" : "int()"; + case DOUBLE: return a_prefix ? "::float()" : "float()"; + case BOOL : return a_prefix ? "::bool()" : "bool()"; + case ATOM : return a_prefix ? "::atom()" : "atom()"; + case STRING: return a_prefix ? "::string()" : "string()"; + case BINARY: return a_prefix ? "::binary()" : "binary()"; + case PID : return a_prefix ? "::pid()" : "pid()"; + case PORT : return a_prefix ? "::port()" : "port()"; + case REF : return a_prefix ? "::ref()" : "ref()"; + case VAR : return a_prefix ? "::var()" : "var()"; + case TUPLE : return a_prefix ? "::tuple()" : "tuple()"; + case LIST : return a_prefix ? "::list()" : "list()"; + case MAP : return a_prefix ? "::map()" : "map()"; + case TRACE : return a_prefix ? "::trace()" : "trace()"; + default : return ""; + } + } + +} // namespace eixx #endif // _EIXX_DEFAULTS_HPP diff --git a/include/eixx/marshal/detail/array_variadic_init.hpp b/include/eixx/marshal/detail/array_variadic_init.hpp new file mode 100644 index 0000000..9e09e57 --- /dev/null +++ b/include/eixx/marshal/detail/array_variadic_init.hpp @@ -0,0 +1,64 @@ +//---------------------------------------------------------------------------- +/// \file array_variadic_init.hpp +//---------------------------------------------------------------------------- +/// \brief Copies variadic parameters to an array +//---------------------------------------------------------------------------- +// Copyright (c) 2013 Serge Aleynikov +// Created: 2013-10-05 +//---------------------------------------------------------------------------- +/* +***** BEGIN LICENSE BLOCK ***** + +Copyright 2010 Serge Aleynikov + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +***** END LICENSE BLOCK ***** +*/ +#ifndef _EIXX_ARRAY_VARIADIC_INIT_HPP_ +#define _EIXX_ARRAY_VARIADIC_INIT_HPP_ + +#include + +namespace eixx { +namespace marshal { +namespace detail { + + namespace { + template + struct init_array { + static void set(eterm* a_array, const Head& v, const Tail&... tail) { + a_array[I] = eterm(v); + init_array::set(a_array, tail...); + } + }; + + template + struct init_array { + static void set(eterm* a_array, const Head& v) { + a_array[I] = eterm(v); + } + }; + } + + template + void initialize(eterm* a_target, Args... args) + { + init_array<0, Args...>::set(a_target, args...); + } + +} // namespace detail +} // namespace marshal +} // namespace eixx + +#endif // _EIXX_ARRAY_VARIADIC_INIT_HPP_ diff --git a/include/eixx/marshal/endian.hpp b/include/eixx/marshal/endian.hpp index 567cd3a..d692d4a 100644 --- a/include/eixx/marshal/endian.hpp +++ b/include/eixx/marshal/endian.hpp @@ -9,21 +9,19 @@ /* ***** BEGIN LICENSE BLOCK ***** -Copyright (C) 2010 Serge Aleynikov +Copyright 2010 Serge Aleynikov -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. + http://www.apache.org/licenses/LICENSE-2.0 -You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. ***** END LICENSE BLOCK ***** */ @@ -31,32 +29,53 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #ifndef _EIXX_ENDIAN_HPP_ #define _EIXX_ENDIAN_HPP_ +#include #include +#if BOOST_VERSION >= 107100 +#include +#else #include +#endif -namespace EIXX_NAMESPACE { +namespace eixx { + +namespace { +#if BOOST_VERSION >= 107100 + namespace bsd = boost::endian; +#elif BOOST_VERSION >= 104800 + namespace bsd = boost::spirit::detail; +#else + namespace bsd = boost::detail; +#endif +} + +template +inline void store_be(char* s, T n) { +#if BOOST_VERSION >= 107100 + bsd::endian_store(reinterpret_cast(s), n); +#else + bsd::store_big_endian(static_cast(s), n); +#endif +} template inline void put_be(char*& s, T n) { - #if BOOST_VERSION >= 104900 - boost::spirit::detail::store_big_endian(s, n); - #else - boost::detail::store_big_endian(s, n); - #endif + store_be(s, n); s += sizeof(T); } template inline T cast_be(const char* s) { - #if BOOST_VERSION >= 104900 - return boost::spirit::detail::load_big_endian(s); - #else - return boost::detail::load_big_endian(s); - #endif +#if BOOST_VERSION >= 107100 + return bsd::endian_load + (reinterpret_cast(s)); +#else + return bsd::load_big_endian((const void*)s); +#endif } -template -inline void get_be(const char*& s, T& n) { +template +inline void get_be(const Ch*& s, T& n) { n = cast_be(s); s += sizeof(T); } @@ -71,6 +90,6 @@ inline uint16_t get16be(const char*& s) { uint16_t n; get_be(s, n); return n; } inline uint32_t get32be(const char*& s) { uint32_t n; get_be(s, n); return n; } inline uint64_t get64be(const char*& s) { uint64_t n; get_be(s, n); return n; } -} // namespace EIXX_NAMESPACE +} // namespace eixx #endif // _EIXX_ENDIAN_HPP_ diff --git a/include/eixx/marshal/eterm.hpp b/include/eixx/marshal/eterm.hpp index bcae08c..b3ba28a 100644 --- a/include/eixx/marshal/eterm.hpp +++ b/include/eixx/marshal/eterm.hpp @@ -10,33 +10,28 @@ /* ***** BEGIN LICENSE BLOCK ***** -This file is part of the eixx (Erlang C++ Interface) Library. +Copyright 2010 Serge Aleynikov -Copyright (C) 2010 Serge Aleynikov +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. + http://www.apache.org/licenses/LICENSE-2.0 -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. ***** END LICENSE BLOCK ***** */ #ifndef _EIXX_MARSHAL_ETERM_HPP_ #define _EIXX_MARSHAL_ETERM_HPP_ -#include -#include -#include -#include +#include + +#include #include // Must be included before any @@ -48,29 +43,33 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include #include #include +#include #include #include #include #include -namespace EIXX_NAMESPACE { +namespace eixx { + using marshal::config; + namespace marshal { namespace { template struct enum_type; - template struct enum_type { typedef long type; }; - template struct enum_type { typedef double type; }; - template struct enum_type { typedef bool type; }; - template struct enum_type { typedef atom type; }; - template struct enum_type, Alloc> { typedef string type; }; - template struct enum_type, Alloc> { typedef binary type; }; - template struct enum_type, Alloc> { typedef epid type; }; - template struct enum_type, Alloc> { typedef port type; }; - template struct enum_type, Alloc> { typedef ref type; }; - template struct enum_type, Alloc> { typedef var type; }; - template struct enum_type, Alloc> { typedef tuple type; }; - template struct enum_type, Alloc> { typedef list type; }; - template struct enum_type, Alloc> { typedef trace type; }; + template struct enum_type { using type = long ; }; + template struct enum_type { using type = double ; }; + template struct enum_type { using type = bool ; }; + template struct enum_type { using type = atom ; }; + template struct enum_type, Alloc> { using type = string; }; + template struct enum_type, Alloc> { using type = binary; }; + template struct enum_type, Alloc> { using type = epid ; }; + template struct enum_type, Alloc> { using type = port ; }; + template struct enum_type, Alloc> { using type = ref ; }; + template struct enum_type { using type = var ; }; + template struct enum_type, Alloc> { using type = tuple ; }; + template struct enum_type, Alloc> { using type = list ; }; + template struct enum_type, Alloc> { using type = map ; }; + template struct enum_type, Alloc> { using type = trace ; }; } /** @@ -129,85 +128,177 @@ namespace { template class eterm { eterm_type m_type; - +public: union vartype { - long i; - double d; - bool b; - // Since a union cannot have types that have constructors, - // we allocate space for a pointer to a compound type, and using - // that space to store the value of the compound type. - // Additionally, we ensure that the size of each compound type - // is sizeof(void*). Therefore it's safe to store the actual - // value of a compound type in this union, so the pointer serves - // as the value placeholder. - // This trick allows us to have minimum overhead related to + double d; + bool b; + long i; + atom a; + var v; + string s; + binary bin; + epid pid; + port prt; + ref r; + tuple t; + list l; + map m; + trace trc; + + uint64_t value; // this is for ease of copying + + // We ensure that the size of each compound type + // is sizeof(uint64_t). Therefore it's safe to store the actual + // value of a compound type in this union, so the uint64 integer + // acts as the value placeholder. + // This allows us to have the minimum overhead related to // copying terms as for simple types it merely involves copying - // 16 bytes (64-bit platform) and for compound types it means copying + // 16 bytes and for compound types it means copying // the same 16 bytes and in some cases // incrementing compound type's reference count. // This approach was tested against boost::variant<> and was found // to be several times more efficient. - void* p; /* Space for any compound reference-counted value with constructor */ - - template - operator T& () { return reinterpret_cast(p); } - template - operator const T& () const { return reinterpret_cast(p); } + vartype(int x) : i(x) {} + vartype(long x) : i(x) {} + vartype(double x) : d(x) {} + vartype(bool x) : b(x) {} + vartype(atom x) : a(x) {} + vartype(var x) : v(x) {} + vartype(const string& x) : s(x) {} + vartype(const binary& x) : bin(x) {} + vartype(const epid& x) : pid(x) {} + vartype(const port& x) : prt(x) {} + vartype(const ref& x) : r(x) {} + vartype(const tuple& x) : t(x) {} + vartype(const list& x) : l(x) {} + vartype(const map& x) : m(x) {} + vartype(const trace& x) : trc(x) {} + + vartype(string&& x) : s(std::move(x)) {} + vartype(binary&& x) : bin(std::move(x)) {} + vartype(epid&& x) : pid(std::move(x)) {} + vartype(port&& x) : prt(std::move(x)) {} + vartype(ref&& x) : r(std::move(x)) {} + vartype(tuple&& x) : t(std::move(x)) {} + vartype(list&& x) : l(std::move(x)) {} + vartype(map&& x) : m(std::move(x)) {} + vartype(trace&& x) : trc(std::move(x)) {} + + vartype() : i(0) {} + ~vartype() {} + + void reset() { i = 0; } } vt; + static_assert(sizeof(vartype) == sizeof(uint64_t), "Invalid class size!"); + void check(eterm_type tp) const { if (unlikely(m_type != tp)) throw err_wrong_type(tp, m_type); } /** * Decode a term from the Erlang external binary format. + * @throw err_decode_exception */ - void decode(const char* a_buf, int& idx, size_t a_size, const Alloc& a_alloc) - throw (err_decode_exception); + void decode(const char* a_buf, uintptr_t& idx, size_t a_size, const Alloc& a_alloc); long& get(long*) { check(LONG); return vt.i; } double& get(double*) { check(DOUBLE); return vt.d; } bool& get(bool*) { check(BOOL); return vt.b; } - atom& get(const atom*) { check(ATOM); return vt; } - string& get(const string*) { check(STRING); return vt; } - binary& get(const binary*) { check(BINARY); return vt; } - epid& get(const epid*) { check(PID); return vt; } - port& get(const port*) { check(PORT); return vt; } - ref& get(const ref*) { check(REF); return vt; } - var& get(const var*) { check(VAR); return vt; } - tuple& get(const tuple*) { check(TUPLE); return vt; } - list& get(const list*) { check(LIST); return vt; } - trace& get(const trace*) { check(TRACE); return vt; } + atom& get(const atom*) { check(ATOM); return vt.a; } + var& get(const var*) { check(VAR); return vt.v; } + string& get(const string*) { check(STRING); return vt.s; } + binary& get(const binary*) { check(BINARY); return vt.bin; } + epid& get(const epid*) { check(PID); return vt.pid; } + port& get(const port*) { check(PORT); return vt.prt; } + ref& get(const ref*) { check(REF); return vt.r; } + tuple& get(const tuple*) { check(TUPLE); return vt.t; } + list& get(const list*) { check(LIST); return vt.l; } + map& get(const map*) { check(MAP); return vt.m; } + trace& get(const trace*) { check(TRACE); return vt.trc; } template friend T& get(eterm& t); + void reset() { m_type = UNDEFINED; vt.reset(); } + + void replace(eterm* a) { + m_type = a->m_type; + vt.value = a->vt.value; + a->reset(); + } + + /// @throw err_format_exception + static eterm format(const Alloc& a_alloc, const char** fmt, va_list* args); + + /// @throw err_format_exception + static void format(const Alloc& a_alloc, atom& m, atom& f, eterm& args, + const char** fmt, va_list* pa); public: eterm_type type() const { return m_type; } const char* type_string() const; - eterm() : m_type(UNDEFINED) { vt.p = NULL; } + eterm() : m_type(UNDEFINED) { + static_assert(sizeof(eterm)==2*sizeof(uint64_t), "Invalid size!"); + } - eterm(unsigned int a) : m_type(LONG) { vt.i = a; } - eterm(unsigned long a) : m_type(LONG) { vt.i = a; } - eterm(int a) : m_type(LONG) { vt.i = a; } + eterm(unsigned int a) : m_type(LONG), vt((int)a) {} + eterm(unsigned long a) : m_type(LONG), vt((long)a) {} + eterm(int a) : m_type(LONG), vt(a) {} - eterm(long a) : m_type(LONG) { vt.i = a; } - eterm(double a) : m_type(DOUBLE) { vt.d = a; } - eterm(bool a) : m_type(BOOL) { vt.b = a; } + eterm(long a) : m_type(LONG), vt(a) {} + eterm(double a) : m_type(DOUBLE),vt(a) {} + eterm(bool a) : m_type(BOOL), vt(a) {} + eterm(atom a) : m_type(ATOM), vt(a) {} + eterm(var a) : m_type(VAR), vt(a) {} eterm(const char* a, const Alloc& alloc = Alloc()) - : m_type(STRING) { new (&vt.p) string(a, alloc); } + : m_type(STRING), vt(string(a, alloc)) {} eterm(const std::string& a, const Alloc& alloc = Alloc()) - : m_type(STRING) { new (&vt.p) string(a.c_str(), a.size(), alloc); } - eterm(const atom& a) : m_type(ATOM) { new (&vt.p) atom(a); } - eterm(const string& a) : m_type(STRING) { new (&vt.p) string(a);} - eterm(const binary& a) : m_type(BINARY) { new (&vt.p) binary(a);} - eterm(const epid& a) : m_type(PID) { new (&vt.p) epid(a); } - eterm(const port& a) : m_type(PORT) { new (&vt.p) port(a); } - eterm(const ref& a) : m_type(REF) { new (&vt.p) ref(a); } - eterm(const var& a) : m_type(VAR) { new (&vt.p) var(a); } - eterm(const tuple& a) : m_type(TUPLE) { new (&vt.p) tuple(a); } - eterm(const list& a) : m_type(LIST) { new (&vt.p) list(a); } - eterm(const trace& a) : m_type(TRACE) { new (&vt.p) trace(a); } + : m_type(STRING), vt(string(a.c_str(), a.size(), alloc)) {} + eterm(const string& a) : m_type(STRING), vt(a) {} + eterm(const binary& a) : m_type(BINARY), vt(a) {} + eterm(const epid& a) : m_type(PID), vt(a) {} + eterm(const port& a) : m_type(PORT), vt(a) {} + eterm(const ref& a) : m_type(REF), vt(a) {} + eterm(const tuple& a) : m_type(TUPLE), vt(a) {} + eterm(const list& a) : m_type(LIST), vt(a) {} + eterm(const map& a) : m_type(MAP), vt(a) {} + eterm(const trace& a) : m_type(TRACE), vt(a) {} + + eterm(string&& a) : m_type(STRING), vt(std::move(a)) {} + eterm(binary&& a) : m_type(BINARY), vt(std::move(a)) {} + eterm(epid&& a) : m_type(PID), vt(std::move(a)) {} + eterm(port&& a) : m_type(PORT), vt(std::move(a)) {} + eterm(ref&& a) : m_type(REF), vt(std::move(a)) {} + eterm(tuple&& a) : m_type(TUPLE), vt(std::move(a)) {} + eterm(list&& a) : m_type(LIST), vt(std::move(a)) {} + eterm(map&& a) : m_type(MAP), vt(std::move(a)) {} + eterm(trace&& a) : m_type(TRACE), vt(std::move(a)) {} + + /** + * Copy construct a term from another one. The term is copied by value + * and for compound terms the storage is reference counted. + */ + eterm(const eterm& a) : m_type(a.m_type) { + switch (m_type) { + case STRING: { new (&vt.s) string(a.vt.s); break; } + case BINARY: { new (&vt.bin) binary(a.vt.bin); break; } + case PID: { new (&vt.pid) epid(a.vt.pid); break; } + case PORT: { new (&vt.prt) port(a.vt.prt); break; } + case REF: { new (&vt.r) ref(a.vt.r); break; } + case TUPLE: { new (&vt.t) tuple(a.vt.t); break; } + case LIST: { new (&vt.l) list(a.vt.l); break; } + case MAP: { new (&vt.m) map(a.vt.m); break; } + case TRACE: { new (&vt.trc) trace(a.vt.trc); break; } + default: + vt.value = a.vt.value; + } + } + + /// Move constructor + eterm(eterm&& a) { replace(&a); } + + /// Tuple initialization + eterm(std::initializer_list> items, const Alloc& alloc = Alloc()) + : eterm(tuple(items, alloc)) {} /** * Construct a term by decoding it from the begining of @@ -217,8 +308,7 @@ class eterm { * @param a_size is the total size of the term stored in \a a_buf buffer. * @param a_alloc is the custom allocator. */ - eterm(const char* a_buf, size_t a_size, const Alloc& a_alloc = Alloc()) - throw(err_decode_exception); + eterm(const char* a_buf, size_t a_size, const Alloc& a_alloc = Alloc()); /** * Construct a term by decoding it from an \a idx offset of the @@ -229,33 +319,10 @@ class eterm { * @param a_size is the total size of the term stored in \a a_buf buffer. * @param a_alloc is the custom allocator. */ - eterm(const char* a_buf, int& idx, size_t a_size, const Alloc& a_alloc = Alloc()) - throw(err_decode_exception) { + eterm(const char* a_buf, uintptr_t& idx, size_t a_size, const Alloc& a_alloc = Alloc()) { decode(a_buf, idx, a_size, a_alloc); } - /** - * Copy construct a term from another one. The term is copied by value - * and for compound terms the storage is reference counted. - */ - eterm(const eterm& a) : m_type(a.m_type) { - BOOST_STATIC_ASSERT(sizeof(vartype) == sizeof(void*)); - switch (m_type) { - case ATOM: { const atom& t = a.vt; new (&vt.p) atom(t); break; } - case STRING: { const string& t = a.vt; new (&vt.p) string(t); break; } - case BINARY: { const binary& t = a.vt; new (&vt.p) binary(t); break; } - case PID: { const epid& t = a.vt; new (&vt.p) epid(t); break; } - case PORT: { const port& t = a.vt; new (&vt.p) port(t); break; } - case REF: { const ref& t = a.vt; new (&vt.p) ref(t); break; } - case VAR: { const var& t = a.vt; new (&vt.p) var(t); break; } - case TUPLE: { const tuple& t = a.vt; new (&vt.p) tuple(t); break; } - case LIST: { const list& t = a.vt; new (&vt.p) list(t); break; } - case TRACE: { const trace& t = a.vt; new (&vt.p) trace(t); break; } - default: - vt.i = a.vt.i; - } - } - /** * Destruct this term. For compound terms it decreases the * reference count of their storage. This does nothing to @@ -264,16 +331,15 @@ class eterm { ~eterm() { switch (m_type) { //No need to destruct atoms - they are stored in global atom table. - //case ATOM: { atom& v = vt; v.~atom(); return; } - case STRING: { string& v = vt; v.~string(); return; } - case BINARY: { binary& v = vt; v.~binary(); return; } - case PID: { epid& v = vt; v.~epid(); return; } - case PORT: { port& v = vt; v.~port(); return; } - case REF: { ref& v = vt; v.~ref(); return; } - case VAR: { var& v = vt; v.~var(); return; } - case TUPLE: { tuple& v = vt; v.~tuple(); return; } - case LIST: { list& v = vt; v.~list(); return; } - case TRACE: { trace& v = vt; v.~trace(); return; } + case STRING: { vt.s.~string(); return; } + case BINARY: { vt.bin.~binary(); return; } + case PID: { vt.pid.~epid(); return; } + case PORT: { vt.prt.~port(); return; } + case REF: { vt.r.~ref(); return; } + case TUPLE: { vt.t.~tuple(); return; } + case LIST: { vt.l.~list(); return; } + case MAP: { vt.m.~map(); return; } + case TRACE: { vt.trc.~trace(); return; } default: return; } } @@ -287,11 +353,37 @@ class eterm { // For some reason the template version above doesn't work for eterm parameter // so we overload it explicitely. - void operator= (const eterm& a) { if (this != &a) set(a); } + eterm& operator= (const eterm& a) { if (this != &a) set(a); return *this; } + + /** + * Assign the value to this term. If current term has been initialized, + * its old value is destructed. + */ + eterm& operator= (eterm&& a) { + if (this != &a) { + if (m_type >= STRING) this->~eterm(); + replace(&a); + } + return *this; + } + + template + void operator= (T&& a) { + if (m_type >= STRING) this->~eterm(); + new (this) eterm(std::move(a)); + } + + /** + * Check that one term is less than the other. The function returns true + * if the term's type is less than the type of term "rhs" accorting to + * type_precedence() or if they have identical precedence, the check is + * made that the terms value is less than the value of the "rhs" term. + */ + bool operator<(const eterm& rhs) const; template void set(const T& a) { - if (m_type > ATOM) + if (m_type >= STRING) this->~eterm(); new (this) eterm(a); } @@ -315,9 +407,9 @@ class eterm { */ bool initialized() const { switch (type()) { - case TUPLE: { const tuple& v = vt; return v.initialized(); } - case LIST: { const list& v = vt; return v.initialized(); } - case TRACE: { const trace& v = vt; return v.initialized(); } + case TUPLE: { return vt.t.initialized(); } + case LIST: { return vt.l.initialized(); } + case TRACE: { return vt.trc.initialized(); } default: return true; } } @@ -328,15 +420,45 @@ class eterm { * they point to the same storage */ bool equals(const eterm& rhs) const { - return m_type == rhs.m_type && vt.i == rhs.vt.i; + return m_type == rhs.m_type && vt.value == rhs.vt.value; } /** * Get the string representation of this eterm using a variable binding * @param binding Variable binding to use. It can be null. */ - std::string to_string(size_t a_size_limit = std::string::npos, - const varbind* binding = NULL) const; + std::string to_string(size_t a_size_limit, + const varbind* binding = NULL) const; + + // Separated into a separate function without default args for ease of gdb debugging + std::string to_string() const { return to_string(std::string::npos, NULL); } + + // Return the term as a value of given type. + // NOTE: only integer | double | bool | string types are supported + template + typename std::enable_if::value, double>::type + get() const { return to_double(); } + + template + typename std::enable_if::value, bool>::type + get() const { return to_bool(); } + + template + typename std::enable_if::value || + std::is_same::value || + std::is_same::value || + std::is_same::value || + std::is_same::value || + std::is_same::value || + std::is_same::value || + std::is_same::value || + std::is_same::value + , T>::type + get() const { return T(to_long()); } + + template + typename std::enable_if::value, std::string>::type + get() const { return to_str().to_str(); } // Convert a term to its underlying type. Will throw an exception // when the underlying type doesn't correspond to the requested operation. @@ -344,19 +466,45 @@ class eterm { long to_long() const { check(LONG); return vt.i; } double to_double() const { check(DOUBLE); return vt.d; } bool to_bool() const { check(BOOL); return vt.b; } - const atom& to_atom() const { check(ATOM); return vt; } - const string& to_str() const { check(STRING); return vt; } - const binary& to_binary() const { check(BINARY); return vt; } - const epid& to_pid() const { check(PID); return vt; } - const port& to_port() const { check(PORT); return vt; } - const ref& to_ref() const { check(REF); return vt; } - const var& to_var() const { check(VAR); return vt; } - const tuple& to_tuple() const { check(TUPLE); return vt; } - tuple& to_tuple() { check(TUPLE); return vt; } - const list& to_list() const { check(LIST); return vt; } - list& to_list() { check(LIST); return vt; } - const trace& to_trace() const { check(TRACE); return vt; } - trace& to_trace() { check(TRACE); return vt; } + const atom& to_atom() const { check(ATOM); return vt.a; } + const var& to_var() const { check(VAR); return vt.v; } + const string& to_str() const { + if (m_type==LIST && vt.l.empty()) return string::null(); + check(STRING); return vt.s; + } + const std::string as_str() const { + static const std::string s_null; + return m_type==LIST && vt.l.empty() + ? s_null + : m_type==STRING + ? vt.s.to_str() : to_string(); + } + const binary& to_binary() const { check(BINARY); return vt.bin; } + const epid& to_pid() const { check(PID); return vt.pid; } + const port& to_port() const { check(PORT); return vt.prt; } + const ref& to_ref() const { check(REF); return vt.r; } + const tuple& to_tuple() const { check(TUPLE); return vt.t; } + tuple& to_tuple() { check(TUPLE); return vt.t; } + const list& to_list() const { check(LIST); return vt.l; } + list& to_list() { check(LIST); return vt.l; } + const map& to_map() const { check(MAP); return vt.m; } + map& to_map() { check(MAP); return vt.m; } + const trace& to_trace() const { check(TRACE); return vt.trc; } + trace& to_trace() { check(TRACE); return vt.trc; } + + // Try to decode the value as a pair containing atom + // option name and any value + bool to_pair(atom& a_opt, eterm& a_val) { + static const eterm s_pair = eterm::format("{A::atom(), V}"); + static const atom s_am_opt = atom("A"); + static const atom s_am_val = atom("V"); + + varbind binding; + if (!match(s_pair, &binding)) return false; + a_opt = binding[s_am_opt]->to_atom(); + a_val = *binding[s_am_val]; + return true; + } // Checks if database of the term is of given type @@ -372,6 +520,7 @@ class eterm { bool is_var() const { return m_type == VAR ; } bool is_tuple() const { return m_type == TUPLE ; } bool is_list() const { return m_type == LIST ; } + bool is_map() const { return m_type == MAP ; } bool is_trace() const { return m_type == TRACE ; } /** @@ -380,11 +529,15 @@ class eterm { * @param binding varbind to use in pattern matching. * This binding will be updated with new bound variables if * match succeeds. + * @throw err_unbound_variable * @return true if matching succeeded or false if failed. */ - bool match(const eterm& pattern, varbind* binding = NULL, - const Alloc& a_alloc = Alloc()) const - throw (err_unbound_variable); + bool match(const eterm& pattern, varbind* binding, + const Alloc& a_alloc = Alloc()) const; + + // Separated into a separate function without default args for ease of gdb debugging + /// @throw err_unbound_variable + bool match(const eterm& pattern) const { return match(pattern, NULL, Alloc()); } /** * Returns the equivalent without inner variables, using the @@ -399,11 +552,16 @@ class eterm { * err_unbound_variable if there is a variable. * @returns a smart pointer to the new term with all eterm_var * variables replaced by bound values. - * @throws err_invalid_term if the term is invalid - * @throws err_unbound_variable if a variable is unbound + * @throw err_invalid_term if the term is invalid + * @throw err_unbound_variable if a variable is unbound */ - bool subst(eterm& out, const varbind* binding) const - throw (err_invalid_term, err_unbound_variable); + bool subst(eterm& out, const varbind* binding) const; + + /** Substitutes all variables in the term \a a. + * @throw err_invalid_term + * @throw err_unbound_variable + */ + eterm apply(const varbind& binding) const; /** * This method finds the first unbound variable in a term for @@ -443,10 +601,10 @@ class eterm { * @param a_header_size is the size of packet header (valid values: 0, 1, 2, 4). * @param a_with_version indicates if a magic version byte * needs to be encoded in the beginning of the buffer. + * @throw err_encode_exception */ void encode(char* buf, size_t size, - size_t a_header_size = DEF_HEADER_SIZE, bool a_with_version = true) const - throw (err_encode_exception); + size_t a_header_size = DEF_HEADER_SIZE, bool a_with_version = true) const; /** * Create an eterm from an string representation. Like sprintf() @@ -456,13 +614,13 @@ class eterm { * * The set of valid format specifiers is as follows: *
    - *
  • a - An atom - *
  • s - A string - *
  • i - An integer - *
  • l - A long integer - *
  • u - An unsigned long integer - *
  • f - A double float - *
  • w - A pointer to some arbitrary term passed as argument + *
  • a - An atom
  • + *
  • s - A string
  • + *
  • i - An integer
  • + *
  • l - A long integer
  • + *
  • u - An unsigned long integer
  • + *
  • f - A double float
  • + *
  • w - A pointer to some arbitrary term passed as argument
  • *
* * Example: @@ -471,12 +629,19 @@ class eterm { * "alex", 40, eterm_t("1955-10-1")); * * @return compiled eterm - * @throws err_format_exception + * @throw err_format_exception + */ + static eterm format(const Alloc& a_alloc, const char* fmt, ...); + static eterm format(const char* fmt, ...); + + /** + * Same as format(a_alloc, fmt, ...), but parses string in format: + * "Module:Function(Arg1, Arg2, ...) + * @throw err_format_exception */ - static eterm format(const Alloc& a_alloc, const char* fmt, ...) - throw (err_format_exception); - static eterm format(const char* fmt, ...) - throw (err_format_exception); + static void format(const Alloc& a_alloc, atom& mod, atom& fun, eterm& args, + const char* fmt, ...); + static void format(atom& mod, atom& fun, eterm& args, const char* fmt, ...); /// Cast a value to eterm. If t is of eterm type, it is returned as is. template @@ -492,21 +657,22 @@ class eterm { case LONG: return wrapper(v, vt.i); case DOUBLE: return wrapper(v, vt.d); case BOOL: return wrapper(v, vt.b); - case ATOM: { const atom& t = vt; return wrapper(v, t); } - case STRING: { const string& t = vt; return wrapper(v, t); } - case BINARY: { const binary& t = vt; return wrapper(v, t); } - case PID: { const epid& t = vt; return wrapper(v, t); } - case PORT: { const port& t = vt; return wrapper(v, t); } - case REF: { const ref& t = vt; return wrapper(v, t); } - case VAR: { const var& t = vt; return wrapper(v, t); } - case TUPLE: { const tuple& t = vt; return wrapper(v, t); } - case LIST: { const list& t = vt; return wrapper(v, t); } - case TRACE: { const trace& t = vt; return wrapper(v, t); } + case ATOM: return wrapper(v, vt.a); + case VAR: return wrapper(v, vt.v); + case STRING: return wrapper(v, vt.s); + case BINARY: return wrapper(v, vt.bin); + case PID: return wrapper(v, vt.pid); + case PORT: return wrapper(v, vt.prt); + case REF: return wrapper(v, vt.r); + case TUPLE: return wrapper(v, vt.t); + case LIST: return wrapper(v, vt.l); + case MAP: return wrapper(v, vt.m); + case TRACE: return wrapper(v, vt.trc); default: { std::stringstream s; s << "Undefined term_type (" << m_type << ')'; throw err_invalid_term(s.str()); } - BOOST_STATIC_ASSERT(MAX_ETERM_TYPE == 13); + BOOST_STATIC_ASSERT(MAX_ETERM_TYPE == 14); } } }; @@ -516,15 +682,15 @@ template T& get(eterm& t) { } } // namespace marshal -} // namespace EIXX_NAMESPACE +} // namespace eixx namespace std { template - ostream& operator<< (ostream& out, const EIXX_NAMESPACE::marshal::eterm& a_term) { + ostream& operator<< (ostream& out, const eixx::marshal::eterm& a_term) { return out << a_term.to_string(); } } -#include +#include #endif diff --git a/include/eixx/marshal/eterm.hxx b/include/eixx/marshal/eterm.hxx new file mode 100644 index 0000000..e722805 --- /dev/null +++ b/include/eixx/marshal/eterm.hxx @@ -0,0 +1,490 @@ +//---------------------------------------------------------------------------- +/// \file eterm.hxx +//---------------------------------------------------------------------------- +/// \brief Implementation of eterm member functions. +//---------------------------------------------------------------------------- +// Copyright (c) 2010 Serge Aleynikov +// Created: 2010-09-20 +//---------------------------------------------------------------------------- +/* +***** BEGIN LICENSE BLOCK ***** + +Copyright 2010 Serge Aleynikov + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +***** END LICENSE BLOCK ***** +*/ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace eixx { + +inline eterm_type type_string_to_type(const char* s, size_t n) { + eterm_type r = UNDEFINED; + + if (n < 3) return r; + + size_t m = n - 1; + const char* p = s+1; + + switch (s[0]) { + case 'i': + if (strncmp(p,"nt",m)==0) r = LONG; + else if (strncmp(p,"nteger",m)==0) r = LONG; + break; + case 'd': + if (strncmp(p,"ouble",m) == 0) r = DOUBLE; + break; + case 'f': + if (strncmp(p,"loat",m) == 0) r = DOUBLE; + break; + case 'b': + if (strncmp(p,"ool",m) == 0) r = BOOL; + else if (strncmp(p,"inary",m) == 0) r = BINARY; + else if (strncmp(p,"oolean",m)== 0) r = BOOL; + else if (strncmp(p,"yte",m) == 0) r = LONG; + break; + case 'c': + if (strncmp(p,"har",m) == 0) r = LONG; + break; + case 'a': + if (strncmp(p,"tom",m) == 0) r = ATOM; + break; + case 's': + if (strncmp(p,"tring",m) == 0) r = STRING; + break; + case 'p': + if (strncmp(p,"id",m) == 0) r = PID; + else if (strncmp(p,"ort",m) == 0) r = PORT; + break; + case 'r': + if (strncmp(p,"ef",m) == 0) r = REF; + else if (strncmp(p,"eference",m)==0)r = REF; + break; + case 'v': + if (strncmp(p,"ar",m) == 0) r = VAR; + break; + case 't': + if (strncmp(p,"uple",m) == 0) r = TUPLE; + else if (strncmp(p,"race",m) == 0) r = TRACE; + break; + case 'l': + if (strncmp(p,"ist",m) == 0) r = LIST; + break; + case 'm': + if (strncmp(p,"ap",m) == 0) r = MAP; + break; + default: + break; + } + return r; + } + +namespace marshal { + +template +const char* eterm::type_string() const { + switch (m_type) { + case UNDEFINED: return "undefined"; + case LONG: return "long"; + case DOUBLE: return "double"; + case BOOL: return "bool"; + case ATOM: return "atom"; + case STRING: return "string"; + case BINARY: return "binary"; + case PID: return "pid"; + case PORT: return "port"; + case REF: return "ref"; + case VAR: return "var"; + case TUPLE: return "tuple"; + case LIST: return "list"; + case MAP: return "map"; + case TRACE: return "trace"; + default: throw eterm_exception("Term type not supported: ", int(m_type)); + } + static_assert(MAX_ETERM_TYPE == 14, "Invalid number of terms"); +} + +template +inline bool eterm::operator== (const eterm& rhs) const { + if (m_type != rhs.type()) + return false; + switch (m_type) { + case LONG: return vt.i == rhs.vt.i; + case DOUBLE: return vt.d == rhs.vt.d; + case BOOL: return vt.b == rhs.vt.b; + case ATOM: return vt.a == rhs.vt.a; + case VAR: return vt.v == rhs.vt.v; + case STRING: return vt.s == rhs.vt.s; + case BINARY: return vt.bin == rhs.vt.bin; + case PID: return vt.pid == rhs.vt.pid; + case PORT: return vt.prt == rhs.vt.prt; + case REF: return vt.r == rhs.vt.r; + case TUPLE: return vt.t == rhs.vt.t; + case LIST: return vt.l == rhs.vt.l; + case MAP: return vt.m == rhs.vt.m; + case TRACE: return vt.trc == rhs.vt.trc; + default: { + std::stringstream s; s << "Undefined term_type (" << m_type << ')'; + throw err_invalid_term(s.str()); + } + } + static_assert(MAX_ETERM_TYPE == 14, "Invalid number of terms"); +} + + + + + +template +inline bool eterm::operator< (const eterm& rhs) const { + /// Term comparison precedence + auto type_precedence = [](int type) { + static const int s_precedences[MAX_ETERM_TYPE+1] = { + 9, // UNDEFINED + 0, 0, // LONG, DOUBLE + 1, // BOOL + 2, // ATOM + 13, // VAR + 10, // STRING + 11, // BINARY + 6, // PID + 5, // PORT + 3, // REF + 7, // TUPLE + 10, // LIST + 8, // MAP + 12 // TRACE + }; + return s_precedences[type]; + }; + int a = type_precedence(int(m_type)); + int b = type_precedence(int(rhs.m_type)); + if (a < b) return true; + if (a > b) return false; + // precedences are equal - (note that numeric types have same precedence): + switch (m_type) { + case LONG: return double(vt.i) < (rhs.m_type == LONG ? double(rhs.vt.i) : rhs.vt.d); + case DOUBLE: return vt.d < (rhs.m_type == LONG ? double(rhs.vt.i) : rhs.vt.d); + case BOOL: return vt.b < rhs.vt.b; + case ATOM: return vt.a < rhs.vt.a; + case VAR: return vt.v < rhs.vt.v; + case STRING: return vt.s < rhs.vt.s; + case BINARY: return vt.bin < rhs.vt.bin; + case PID: return vt.pid < rhs.vt.pid; + case PORT: return vt.prt < rhs.vt.prt; + case REF: return vt.r < rhs.vt.r; + case TUPLE: return vt.t < rhs.vt.t; + case LIST: return vt.l < rhs.vt.l; + case MAP: return vt.m < rhs.vt.m; + case TRACE: return vt.trc < rhs.vt.trc; + default: { + std::stringstream s; s << "Undefined term_type (" << m_type << ')'; + throw err_invalid_term(s.str()); + } + } + static_assert(MAX_ETERM_TYPE == 14, "Invalid number of terms"); +} + +template +std::string eterm::to_string(size_t a_size_limit, const varbind* binding) const { + if (m_type == UNDEFINED) + return std::string(); + std::ostringstream out; + visit_eterm_stringify visitor(out, binding); + visitor.apply_visitor(*this); + std::string s = out.str(); + return a_size_limit == std::string::npos + ? s : s.substr(0, std::min(a_size_limit, s.size())); +} + +template +eterm::eterm(const char* a_buf, size_t a_size, const Alloc& a_alloc) +{ + uintptr_t idx = 0; + int vsn; + if (ei_decode_version(a_buf, (int*)&idx, &vsn) < 0) + throw err_decode_exception("Wrong eterm version byte!", idx, vsn); + decode(a_buf, idx, a_size, a_alloc); +} + +template +void eterm::decode(const char* a_buf, uintptr_t& idx, size_t a_size, const Alloc& a_alloc) +{ + BOOST_ASSERT(idx <= INT_MAX); + if (static_cast(idx) == a_size) + throw err_decode_exception("Empty term", idx); + + // check the type of next term: + int type, sz; + + if (ei_get_type(a_buf, (int*)&idx, &type, &sz) < 0) + throw err_decode_exception("Cannot determine term type", idx); + + switch (type) { +#ifdef ERL_SMALL_ATOM_UTF8_EXT + case ERL_SMALL_ATOM_UTF8_EXT: +#endif +#ifdef ERL_ATOM_UTF8_EXT + case ERL_ATOM_UTF8_EXT: +#endif +#ifdef ERL_SMALL_ATOM_EXT + case ERL_SMALL_ATOM_EXT: +#endif + case ERL_ATOM_EXT: { + int b; + uintptr_t i = idx; // TODO: Eliminate this variable when there's is a fix for the bug in ei_decode_boolean + if (ei_decode_boolean(a_buf, (int*)&i, &b) < 0) + new (this) eterm(atom(a_buf, idx, a_size)); + else { + idx = i; + new (this) eterm((bool)b); + } + break; + } + case ERL_LARGE_TUPLE_EXT: + case ERL_SMALL_TUPLE_EXT: { + new (this) eterm(tuple(a_buf, idx, a_size, a_alloc)); + break; + } + case ERL_STRING_EXT: + new (this) eterm(string(a_buf, idx, a_size, a_alloc)); + break; + + case ERL_LIST_EXT: + case ERL_NIL_EXT: { + new (this) eterm(list(a_buf, idx, a_size, a_alloc)); + break; + } + case ERL_SMALL_INTEGER_EXT: + case ERL_SMALL_BIG_EXT: + case ERL_LARGE_BIG_EXT: + case ERL_INTEGER_EXT: { + long long l; + if (ei_decode_longlong(a_buf, (int*)&idx, &l) < 0) + throw err_decode_exception("Failed decoding long value", idx); + new (this) eterm((long)l); + break; + } + case NEW_FLOAT_EXT: + case ERL_FLOAT_EXT: { + double d; + if (ei_decode_double(a_buf, (int*)&idx, &d) < 0) + throw err_decode_exception("Failed decoding double value", idx); + new (this) eterm(d); + break; + } + + case ERL_BINARY_EXT: + new (this) eterm(binary(a_buf, idx, a_size, a_alloc)); + break; + +#ifdef ERL_NEW_PID_EXT + case ERL_NEW_PID_EXT: +#endif + case ERL_PID_EXT: + new (this) eterm(epid(a_buf, idx, a_size, a_alloc)); + break; + +#ifdef ERL_NEWER_REFERENCE_EXT + case ERL_NEWER_REFERENCE_EXT: +#endif +#ifdef ERL_NEW_REFERENCE_EXT + case ERL_NEW_REFERENCE_EXT: +#endif + case ERL_REFERENCE_EXT: + new (this) eterm(ref(a_buf, idx, a_size, a_alloc)); + break; + +#ifdef ERL_NEW_PORT_EXT + case ERL_NEW_PORT_EXT: +#endif + case ERL_PORT_EXT: + new (this) eterm(port(a_buf, idx, a_size, a_alloc)); + break; + + case ERL_MAP_EXT: + new (this) eterm(map(a_buf, idx, a_size, a_alloc)); + break; + + default: + std::ostringstream oss; + oss << "Unknown message content type " << type; + throw err_decode_exception(oss.str(), idx, type); + break; + } +} + +template +size_t eterm::encode_size(size_t a_header_size, bool a_with_version) const +{ + BOOST_ASSERT(m_type != UNDEFINED); + size_t n = visit_eterm_encode_size_calc().apply_visitor(*this); + return a_header_size + n + (a_with_version ? 1 : 0); +} + +template +string eterm::encode(size_t a_header_size, bool a_with_version) const +{ + size_t size = encode_size(a_header_size, a_with_version); + string out(NULL, size); + char* p = const_cast(out.c_str()); + encode(p, size, a_header_size, a_with_version); + return out; +} + +template +void eterm::encode(char* a_buf, size_t size, + size_t a_header_size, bool a_with_version) const +{ + BOOST_ASSERT(size > 0); + size_t msg_sz = size - a_header_size; + switch (a_header_size) { + case 0: + break; + case 1: + store_be(a_buf, static_cast(msg_sz)); + break; + case 2: + store_be(a_buf, static_cast(msg_sz)); + break; + case 4: + store_be(a_buf, static_cast(msg_sz)); + break; + default: { + std::stringstream s; + s << "Bad header size: " << a_header_size; + throw err_encode_exception(s.str()); + } + } + uintptr_t offset = a_header_size; + if (a_with_version) { + BOOST_ASSERT(offset <= INT_MAX); + ei_encode_version(a_buf, (int*)&offset); + } + visit_eterm_encoder visitor(a_buf, offset, size); + visitor.apply_visitor(*this); + BOOST_ASSERT((size_t)offset == size); +} + +template +bool eterm::match( + const eterm& pattern, + varbind* binding, + const Alloc& a_alloc) const +{ + // Protect the given binding. Change it only if the match succeeds. + varbind dirty(a_alloc); + if (!binding) { + visit_eterm_match visitor(pattern, &dirty); + if (!visitor.apply_visitor(*this)) + return false; + } else { + dirty.copy(*binding); + visit_eterm_match visitor(pattern, &dirty); + if (!visitor.apply_visitor(*this)) + return false; + binding->merge(dirty); + } + return true; +} + +template +bool eterm::subst(eterm& out, const varbind* binding) const +{ + visit_eterm_subst visitor(out, binding); + return visitor.apply_visitor(*this); +} + +template +eterm eterm::apply(const varbind& binding) const +{ + eterm out; + visit_eterm_subst visitor(out, &binding); + static const eterm s_null; + return visitor.apply_visitor(*this) ? out : s_null; +} + +template +eterm eterm::format(const Alloc& a_alloc, const char** fmt, va_list* pap) +{ + try { + return eformat(fmt, pap, a_alloc); + } catch (err_format_exception& e) { + e.start(*fmt); + throw; + } catch (...) { + throw err_format_exception("Error parsing expression", *fmt, *fmt); + } +} + +template +void eterm::format(const Alloc& a_alloc, atom& m, atom& f, eterm& args, + const char** fmt, va_list* pap) +{ + try { + eformat(m, f, args, fmt, pap, a_alloc); + } catch (err_format_exception& e) { + e.start(*fmt); + throw; + } catch (...) { + throw err_format_exception("Error parsing expression", *fmt, *fmt); + } +} + +template +eterm eterm::format(const Alloc& a_alloc, const char* fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + try { return format(a_alloc, &fmt, &ap); } catch (...) { va_end(ap); throw; } + va_end(ap); +} + +template +eterm eterm::format(const char* fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + try { return format(Alloc(), &fmt, &ap); } catch (...) { va_end(ap); throw; } + va_end(ap); +} + +template +void eterm::format(const Alloc& a_alloc, atom& m, atom& f, eterm& args, + const char* fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + try { format(a_alloc, m, f, args, &fmt, &ap); } catch (...) { va_end(ap); throw; } + va_end(ap); +} +template +void eterm::format(atom& m, atom& f, eterm& args, const char* fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + try { format(Alloc(), m, f, args, &fmt, &ap); } catch (...) { va_end(ap); throw; } + va_end(ap); +} + +} // namespace marshal +} // namespace eixx diff --git a/include/eixx/marshal/eterm.ipp b/include/eixx/marshal/eterm.ipp deleted file mode 100644 index 53f151d..0000000 --- a/include/eixx/marshal/eterm.ipp +++ /dev/null @@ -1,318 +0,0 @@ -//---------------------------------------------------------------------------- -/// \file eterm.ipp -//---------------------------------------------------------------------------- -/// \brief Implementation of eterm member functions. -//---------------------------------------------------------------------------- -// Copyright (c) 2010 Serge Aleynikov -// Created: 2010-09-20 -//---------------------------------------------------------------------------- -/* -***** BEGIN LICENSE BLOCK ***** - -This file is part of the eixx (Erlang C++ Interface) Library. - -Copyright (C) 2010 Serge Aleynikov - -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -***** END LICENSE BLOCK ***** -*/ -#include -#include -#include -#include -#include -#include -#include - -namespace EIXX_NAMESPACE { -namespace marshal { - -template -const char* eterm::type_string() const { - switch (m_type) { - case UNDEFINED: return "undefined"; - case LONG: return "long"; - case DOUBLE: return "double"; - case BOOL: return "bool"; - case ATOM: return "atom"; - case STRING: return "string"; - case BINARY: return "binary"; - case PID: return "pid"; - case PORT: return "port"; - case REF: return "ref"; - case VAR: return "var"; - case TUPLE: return "tuple"; - case LIST: return "list"; - case TRACE: return "trace"; - } - BOOST_STATIC_ASSERT(MAX_ETERM_TYPE == 13); -} - -template -inline bool eterm::operator== (const eterm& rhs) const { - if (m_type != rhs.type()) - return false; - switch (m_type) { - case LONG: return vt.i == rhs.to_long(); - case DOUBLE: return vt.d == rhs.to_double(); - case BOOL: return vt.b == rhs.to_bool(); - case ATOM: return vt.p == rhs.vt.p; // We just need to check for the same value - case STRING: { const string& t = vt; return t == rhs.to_str(); } - case BINARY: { const binary& t = vt; return t == rhs.to_binary();} - case PID: { const epid& t = vt; return t == rhs.to_pid(); } - case PORT: { const port& t = vt; return t == rhs.to_port(); } - case REF: { const ref& t = vt; return t == rhs.to_ref(); } - case VAR: { const var& t = vt; return t == rhs.to_var(); } - case TUPLE: { const tuple& t = vt; return t == rhs.to_tuple(); } - case LIST: { const list& t = vt; return t == rhs.to_list(); } - case TRACE: { const trace& t = vt; return t == rhs.to_trace(); } - default: { - std::stringstream s; s << "Undefined term_type (" << m_type << ')'; - throw err_invalid_term(s.str()); - } - } - BOOST_STATIC_ASSERT(MAX_ETERM_TYPE == 13); -} - -template -std::string eterm::to_string(size_t a_size_limit, const varbind* binding) const { - if (m_type == UNDEFINED) - return ""; - std::ostringstream out; - visit_eterm_stringify visitor(out, binding); - visitor.apply_visitor(*this); - std::string s = out.str(); - return a_size_limit == std::string::npos - ? s : s.substr(0, std::min(a_size_limit, s.size())); -} - -template -eterm::eterm(const char* a_buf, size_t a_size, const Alloc& a_alloc) - throw(err_decode_exception) -{ - int idx = 0; - int vsn; - if (ei_decode_version(a_buf, &idx, &vsn) < 0) - throw err_decode_exception("Wrong eterm version byte!", vsn); - decode(a_buf, idx, a_size, a_alloc); -} - -template -void eterm::decode(const char* a_buf, int& idx, size_t a_size, const Alloc& a_alloc) - throw(err_decode_exception) -{ - if ((size_t)idx == a_size) - throw err_decode_exception("Empty term", idx); - - // check the type of next term: - int type, sz; - - if (ei_get_type(a_buf, &idx, &type, &sz) < 0) - throw err_decode_exception("Cannot determine term type", idx); - - switch (type) { - case ERL_ATOM_EXT: { - int b; - if (ei_decode_boolean(a_buf, &idx, &b) < 0) - new (this) eterm(atom(a_buf, idx, a_size)); - else - new (this) eterm((bool)b); - break; - } - case ERL_LARGE_TUPLE_EXT: - case ERL_SMALL_TUPLE_EXT: { - new (this) eterm(tuple(a_buf, idx, a_size, a_alloc)); - break; - } - case ERL_STRING_EXT: - new (this) eterm(string(a_buf, idx, a_size, a_alloc)); - break; - - case ERL_LIST_EXT: - case ERL_NIL_EXT: { - new (this) eterm(list(a_buf, idx, a_size, a_alloc)); - break; - } - case ERL_SMALL_INTEGER_EXT: - case ERL_SMALL_BIG_EXT: - case ERL_LARGE_BIG_EXT: - case ERL_INTEGER_EXT: { - long long l; - if (ei_decode_longlong(a_buf, &idx, &l) < 0) - throw err_decode_exception("Failed decoding long value", idx); - new (this) eterm((long)l); - break; - } - case NEW_FLOAT_EXT: - case ERL_FLOAT_EXT: { - double d; - if (ei_decode_double(a_buf, &idx, &d) < 0) - throw err_decode_exception("Failed decoding double value", idx); - new (this) eterm(d); - break; - } - case ERL_BINARY_EXT: - new (this) eterm(binary(a_buf, idx, a_size, a_alloc)); - break; - - case ERL_PID_EXT: - new (this) eterm(epid(a_buf, idx, a_size, a_alloc)); - break; - - case ERL_REFERENCE_EXT: - case ERL_NEW_REFERENCE_EXT: - new (this) eterm(ref(a_buf, idx, a_size, a_alloc)); - break; - - case ERL_PORT_EXT: - new (this) eterm(port(a_buf, idx, a_size, a_alloc)); - break; - - default: - std::ostringstream oss; - oss << "Unknown message content type " << type; - throw err_decode_exception(oss.str()); - break; - } -} - -template -size_t eterm::encode_size(size_t a_header_size, bool a_with_version) const -{ - BOOST_ASSERT(m_type != UNDEFINED); - size_t n = visit_eterm_encode_size_calc().apply_visitor(*this); - return a_header_size + n + (a_with_version ? 1 : 0); -} - -template -string eterm::encode(size_t a_header_size, bool a_with_version) const -{ - size_t size = encode_size(a_header_size, a_with_version); - string out("", size); - char* p = const_cast(out.c_str()); - encode(p, size, a_header_size, a_with_version); - return out; -} - -template -void eterm::encode(char* a_buf, size_t size, - size_t a_header_size, bool a_with_version) const throw (err_encode_exception) -{ - #if BOOST_VERSION >= 104900 - namespace bd = boost::spirit::detail; - #else - namespace bd = boost::detail; - #endif - - BOOST_ASSERT(size > 0); - size_t msg_sz = size - a_header_size; - switch (a_header_size) { - case 0: - break; - case 1: - bd::store_big_endian(a_buf, msg_sz); - break; - case 2: - bd::store_big_endian(a_buf, msg_sz); - break; - case 4: - bd::store_big_endian(a_buf, msg_sz); - break; - default: { - std::stringstream s; - s << "Bad header size: " << a_header_size; - throw err_encode_exception(s.str()); - } - } - int offset = a_header_size; - if (a_with_version) - ei_encode_version(a_buf, &offset); - visit_eterm_encoder visitor(a_buf, offset, size); - visitor.apply_visitor(*this); - BOOST_ASSERT((size_t)offset == size); -} - -template -bool eterm::match( - const eterm& pattern, - varbind* binding, - const Alloc& a_alloc) const - throw (err_unbound_variable) -{ - // Protect the given binding. Change it only if the match succeeds. - varbind dirty(a_alloc); - if (!binding) { - visit_eterm_match visitor(pattern, &dirty); - if (!visitor.apply_visitor(*this)) - return false; - } else { - dirty.copy(*binding); - visit_eterm_match visitor(pattern, &dirty); - if (!visitor.apply_visitor(*this)) - return false; - binding->merge(dirty); - } - return true; -} - -template -bool eterm::subst(eterm& out, const varbind* binding) const - throw (err_invalid_term, err_unbound_variable) { - visit_eterm_subst visitor(out, binding); - return visitor.apply_visitor(*this); -} - -template -eterm eterm::format(const Alloc& a_alloc, const char* fmt, ...) - throw (err_format_exception) -{ - const char** l_fmt = &fmt; - va_list ap; - va_start(ap, fmt); - //BOOST_SCOPE_EXIT( (&ap) ) { va_end(ap); } BOOST_SCOPE_EXIT_END; - try { - eterm res( eformat(l_fmt, &ap, a_alloc) ); - va_end(ap); - return res; - } catch (err_format_exception& e) { - va_end(ap); - e.start(*l_fmt); - throw; - } -} - -template -eterm eterm::format(const char* fmt, ...) - throw (err_format_exception) -{ - const char** l_fmt = &fmt; - va_list ap; - va_start(ap, fmt); - //BOOST_SCOPE_EXIT( (&ap) ) { va_end(ap); } BOOST_SCOPE_EXIT_END; - try { - Alloc alloc; - eterm res = eformat(l_fmt, &ap, alloc); - va_end(ap); - return res; - } catch (err_format_exception& e) { - va_end(ap); - e.start(*l_fmt); - throw; - } -} - -} // namespace marshal -} // namespace EIXX_NAMESPACE diff --git a/include/eixx/marshal/eterm_format.hpp b/include/eixx/marshal/eterm_format.hpp index 3a67980..e5a01f9 100644 --- a/include/eixx/marshal/eterm_format.hpp +++ b/include/eixx/marshal/eterm_format.hpp @@ -9,21 +9,19 @@ /* ***** BEGIN LICENSE BLOCK ***** -Copyright (C) 2010 Serge Aleynikov +Copyright 2010 Serge Aleynikov -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. + http://www.apache.org/licenses/LICENSE-2.0 -You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. ***** END LICENSE BLOCK ***** */ @@ -33,7 +31,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include -namespace EIXX_NAMESPACE { +namespace eixx { namespace marshal { /** @@ -52,14 +50,23 @@ namespace marshal { */ // Forward declaration +/// @throw err_format_exception template -static eterm eformat(const char** fmt, va_list* args, const Alloc& a_alloc = Alloc()) - throw(err_format_exception); +static eterm eformat(const char** fmt, va_list* args, const Alloc& a_alloc = Alloc()); + +/** + * Parse a format string in the form "Module:Function(Args...)" into corresponding + * \a mod, \a fun, \a args + * @throw err_format_exception + */ +template +static void eformat(atom& mod, atom& fun, eterm& args, + const char** fmt, va_list* pa, const Alloc& a_alloc = Alloc()); } // namespace marshal -} // namespace EIXX_NAMESPACE +} // namespace eixx -#include +#include #endif // _EI_ETERM_FORMAT_HPP_ diff --git a/include/eixx/marshal/eterm_format.hxx b/include/eixx/marshal/eterm_format.hxx new file mode 100644 index 0000000..5253401 --- /dev/null +++ b/include/eixx/marshal/eterm_format.hxx @@ -0,0 +1,513 @@ +//---------------------------------------------------------------------------- +/// \file eterm_format.hpp +//---------------------------------------------------------------------------- +/// \brief A class implementing encoding of eterm from a string. +//---------------------------------------------------------------------------- +// Copyright (c) 2010 Serge Aleynikov +// Copyright (c) 2005 Hector Rivas Gandara +// Created: 2010-09-20 +//---------------------------------------------------------------------------- +/* +***** BEGIN LICENSE BLOCK ***** + +Copyright 2010 Serge Aleynikov + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +***** END LICENSE BLOCK ***** +*/ + +/* + * Implementation format functionality + * This is *a copy* of source from erl_interface + * Implementation is largely unfinished. + * + * The Initial Developer of the Original Code is Ericsson Utvecklings AB. + * Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings + * AB. All Rights Reserved.'' + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +//#include + +namespace eixx { +namespace marshal { + + namespace { + + template + struct vector : public + std::vector< + eterm, + typename std::allocator_traits::template rebind_alloc> + > + { + using VecAlloc = typename std::allocator_traits:: + template rebind_alloc>; + using base = std::vector, VecAlloc>; + + explicit vector(const Alloc& a_alloc = Alloc()) : base(a_alloc) + {} + + eterm to_tuple(Alloc& a_alloc) { + eterm& term = (eterm&)*this->begin(); + auto t = tuple(&term, this->size(), a_alloc); + return eterm(t); + } + + eterm to_list(Alloc& a_alloc) { + eterm& term = (eterm&)*this->begin(); + auto t = list(&term, this->size(), a_alloc); + return eterm(t); + } + + const eterm& operator[] (size_t idx) const { + return (const eterm&)*(base::begin()+idx); + } + + const eterm& back() const { + return (const eterm&)*(base::end()-1); + } + }; + + } // namespace + + static void skip_ws_and_comments(const char** fmt) { + bool inside_comment = false; + for(char c = **fmt; c; c = *(++(*fmt))) { + if (inside_comment) { + if (c == '\n') inside_comment = false; + continue; + } else if (c == ' ' || c == '\t' || c == '\n' || c == '\r') { + continue; + } else if (c == '%') { + inside_comment = true; + continue; + } + break; + } + } + + static inline var pvariable(const char **fmt) + { + const char* start = *fmt, *p = start; + char c; + uintptr_t len; + + skip_ws_and_comments(fmt); + + for (c = *p; c && (isalnum((int)c) || (c == '_')); c = *(++p)); + + const char* end = p; + + eterm_type type; + + // TODO: Add recursive type checking + // (i.e. A :: [{atom(), [integer() | string() | double() | tuple()]}]) + if (c == ':' && *(p+1) == ':') { + p += 2; + + const char* tps = p; + + for (c = *p; c && isalnum((int)c); c = *(++p)); + + if (c == '(' && *(p+1) == ')') { + type = type_string_to_type(tps, static_cast(p - tps)); + if (type == UNDEFINED) + throw err_format_exception("Error parsing variable type", start); + p += 2; + } else + throw err_format_exception("Invalid variable type", tps); + } else + type = UNDEFINED; + + *fmt = p; + len = static_cast(end - start); + + return var(start, len, type); + + } /* pvariable */ + + static inline atom patom(const char **fmt) + { + skip_ws_and_comments(fmt); + + const char* start = *fmt, *p = start; + + for(char c = *p; c && (isalnum(c) || c == '_' || c == '@'); c = *(++p)); + + *fmt = p; + + return atom(start, p - start); + } /* patom */ + + static inline atom pquotedatom(const char **fmt) + { + ++(*fmt); /* skip first quote */ + //skip_ws_and_comments(fmt); + + const char* start = *fmt, *p = start; + + for (char c = *p; c && (c != '\'' || *(p-1) == '\\') ; c = *(++p)); + + if (*p != '\'') + throw err_format_exception("Error parsing quotted atom", start); + + *fmt = p+1; /* skip last quote */ + uintptr_t len = static_cast(p - start); + + return atom(start, len); + + } /* pquotedatom */ + + /* Check if integer or float + */ + template + static eterm pdigit(const char **fmt) + { + const char* start = *fmt, *p = start; + bool dotp = false; + int base = 10; + + skip_ws_and_comments(fmt); + + if (*p == '-') p++; + + for (char c = *p; c; c = *(++p)) { + if (isdigit(c)) + continue; + else if (c == '.' && !dotp) + dotp = true; + else if (c == '#') { + long b = strtol(start, NULL, 10); + BOOST_ASSERT(b <= INT_MAX); + base = (int)b; + start = p+1; + } else + break; + } + + *fmt = p; + if (dotp) { + auto d = strtod(start, NULL); + return d; + } + auto n = strtol(start, NULL, base); + return n; + } /* pdigit */ + + template + static eterm pstring(const char **fmt, Alloc& alloc) + { + const char* start = ++(*fmt); /* skip first quote */ + const char* p = start; + + // skip_ws_and_comments(fmt); + + for (char c = *p; c && (c != '"' || *(p-1) == '\\'); c = *(++p)); + + if (*p != '"') + throw err_format_exception("Error parsing string", start); + + *fmt = p+1; /* skip last quote */ + uintptr_t len = static_cast(p - start); + + return eterm(string(start, len, alloc)); + } /* pstring */ + + + /** + * Convert a string with variables into an Erlang term. + * + * The format letters are: + * @li a - An atom + * @li s - A string + * @li i - An integer + * @li l - A long integer + * @li u - An unsigned long integer + * @li f - A double float + * @li w - A pointer to some arbitrary term + * @li v - A pointer to an Erlang variable + * + * @todo Implement support for parsing tail list expressions in the form + * "[H | T]". Currently the pipe notation doesn't work. + */ + template + static eterm pformat(const char** fmt, va_list* pap, Alloc& a_alloc) + { + skip_ws_and_comments(fmt); + + switch (*(*fmt)++) { + case 'v': return *va_arg(*pap, var*); + case 'w': return *va_arg(*pap, eterm*); + case 'a': return atom(va_arg(*pap, char*)); + case 's': return string(va_arg(*pap, char*), a_alloc); + case 'i': return va_arg(*pap, int); + case 'l': return va_arg(*pap, long); + case 'u': return (long)va_arg(*pap, unsigned long); + case 'f': return va_arg(*pap, double); + default: throw err_format_exception("Error parsing string", *fmt-1); + } + } /* pformat */ + + template + static bool ptuple(const char** fmt, va_list* pap, + vector& v, Alloc& a_alloc) + { + bool res = false; + + skip_ws_and_comments(fmt); + + switch (*(*fmt)++) { + + case '}': + res = true; + break; + + case ',': + res = ptuple(fmt, pap, v, a_alloc); + break; + + default: { + (*fmt)--; + v.push_back(eformat(fmt, pap, a_alloc)); + if (v.back().type() != UNDEFINED) + res = ptuple(fmt, pap, v, a_alloc); + break; + } + + } /* switch */ + + return res; + + } /* ptuple */ + + template + static bool plist(const char** fmt, va_list* pap, + vector& v, Alloc& a_alloc) + { + bool res = false; + + skip_ws_and_comments(fmt); + + switch (*(*fmt)++) { + + case ']': + res = true; + break; + + case ',': + res = plist(fmt, pap, v, a_alloc); + break; + + case '|': + skip_ws_and_comments(fmt); + if (isupper((int)**fmt) || (**fmt == '_')) { + var a = pvariable(fmt); + v.push_back(eterm(a)); + skip_ws_and_comments(fmt); + if (**fmt == ']') + res = true; + break; + } + break; + + default: { + (*fmt)--; + auto et = eformat(fmt, pap, a_alloc); + if (et.type() != UNDEFINED) { + v.push_back(et); + res = plist(fmt, pap, v, a_alloc); + } + break; + } + + } /* switch */ + + return res; + + } /* plist */ + + template + static eterm eformat(const char** fmt, va_list* pap, const Alloc& a_alloc) + { + vector v(a_alloc); + Alloc alloc(a_alloc); + eterm ret; + + skip_ws_and_comments(fmt); + + switch (*(*fmt)++) { + case '{': { + if (!ptuple(fmt, pap, v, alloc)) + throw err_format_exception("Error parsing tuple", *fmt); + ret = v.to_tuple(alloc); + break; + } + case '[': + if (**fmt == ']') { + (*fmt)++; + ret = v.to_list(alloc); + } else if (!plist(fmt, pap, v, alloc)) + throw err_format_exception("Error parsing list", *fmt); + ret = v.to_list(alloc); + break; + + case '<': { + if (**fmt != '<') + throw err_format_exception("Error parsing binary", *fmt); + bool str = *(*fmt + 1) == '"'; + if (str) { + *fmt += 2; + const char* end = strstr(*fmt, "\">>"); + if (!end) + throw err_format_exception("Cannot find end of binary", *fmt); + ret = eterm(binary(*fmt, static_cast(end - *fmt), alloc)); + *fmt = end + 3; + } else { + const char* end = strstr(++(*fmt), ">>"); + if (!end) + throw err_format_exception("Cannot find end of binary", *fmt); + std::vector v2; + auto p = *fmt; + + while (p < end) { + while (*p == ' ' || *p == '\t') ++p; + int byte; + auto q = fast_atoi(p, end, byte); + if (!q) + throw err_format_exception("Error parsing binary", p); + p = q; + if (byte < 0 || byte > 255) + throw err_format_exception("Invalid byte value in binary", p); + v2.push_back((char)byte); + while (*p == ' ' || *p == '\t') ++p; + if (*p == ',') + ++p; + else if (p < end) + throw err_format_exception("Invalid byte delimiter in binary", p); + } + auto begin = &v2[0]; + ret = eterm(binary(begin, v2.size(), alloc)); + *fmt = end + 2; + } + break; + } + + case '$': /* char-value? */ + ret = eterm((int)(*(*fmt)++)); + break; + + case '~': + ret = pformat(fmt, pap, alloc); + break; + + default: { + (*fmt)--; + if (islower(**fmt)) /* atom ? */ + ret = patom(fmt); + else if (isupper(**fmt) || (**fmt == '_')) + ret = pvariable(fmt); + else if (isdigit(**fmt) || **fmt == '-') /* int|float ? */ + ret = pdigit(fmt); + else if (**fmt == '"') /* string ? */ + ret = pstring(fmt, alloc); + else if (**fmt == '\'') /* quoted atom ? */ + ret = pquotedatom(fmt); + break; + } + } + + if (ret.empty()) + throw err_format_exception("invalid term", *fmt); + + return ret; + + } /* eformat */ + + template + static void eformat(atom& mod, atom& fun, eterm& args, + const char** fmt, va_list* pap, const Alloc& a_alloc) + { + Alloc alloc(a_alloc); + vector v(alloc); + + skip_ws_and_comments(fmt); + + const char* start = *fmt, *p = start; + const char* q = strchr(p, ':'); + + if (!q) + throw err_format_exception("Module name not found", p); + + mod = atom(p, q - p); + + p = q+1; + + q = strchr(p, '('); + + if (!q) + throw err_format_exception("Function name not found", p); + + fun = atom(p, q - p); + + p = q+1; + + skip_ws_and_comments(&p); + + if (*p == '\0') + throw err_format_exception("Invalid argument syntax", p); + else if (*p == ')') + ++p; + else while(true) { + v.push_back(eformat(&p, pap, a_alloc)); + skip_ws_and_comments(&p); + + char c = *p++; + + if (c == '\0') + throw err_format_exception("Arguments list not closed", &c); + else if (c == ')') + break; + if (c != ',') + throw err_format_exception("Arguments must be comma-delimited", &c); + } + + skip_ws_and_comments(&p); + + if (*p != '\0') { + if (*p == '.') { + ++p; skip_ws_and_comments(&p); + } + } + + if (*p != '\0') + throw err_format_exception("Invalid MFA format", p); + + args = v.to_list(alloc); + } + +} // namespace marshal +} // namespace eixx + diff --git a/include/eixx/marshal/eterm_format.ipp b/include/eixx/marshal/eterm_format.ipp deleted file mode 100644 index 8281216..0000000 --- a/include/eixx/marshal/eterm_format.ipp +++ /dev/null @@ -1,426 +0,0 @@ -//---------------------------------------------------------------------------- -/// \file eterm_format.hpp -//---------------------------------------------------------------------------- -/// \brief A class implementing encoding of eterm from a string. -//---------------------------------------------------------------------------- -// Copyright (c) 2010 Serge Aleynikov -// Copyright (c) 2005 Hector Rivas Gandara -// Created: 2010-09-20 -//---------------------------------------------------------------------------- -/* -***** BEGIN LICENSE BLOCK ***** - -This file is part of the eixx (Erlang C++ Interface) Library. - -Copyright (C) 2010 Serge Aleynikov - -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -***** END LICENSE BLOCK ***** -*/ - -/* - * Implementation format functionality - * This is *a copy* of source from erl_interface - * Implementation is largely unfinished. - * - * The Initial Developer of the Original Code is Ericsson Utvecklings AB. - * Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings - * AB. All Rights Reserved.'' - */ - - -#include -#include -#include -#include -#include -#include -#include -//#include - -namespace EIXX_NAMESPACE { -namespace marshal { - - template - struct vector : public std::vector, Alloc> { - explicit vector(const Alloc& a_alloc = Alloc()) - : std::vector, Alloc> (a_alloc) - {} - }; - - enum { - ERL_OK = 0 - , ERL_FMT_ERR = -1 - , ERL_MAX_ENTRIES = 255 /* Max entries in a tuple/list term */ - , ERL_MAX_NAME_LENGTH = 255 /* Max length of variable names */ - }; - - static void skip_null_chars(const char** fmt) { - for(char c = **fmt; c == ' ' || c == '\t' || c == '\n'; c = *(++(*fmt))); - } - - static char *pvariable(const char **fmt, char *buf) - { - const char* start = *fmt; - char c; - int len; - - skip_null_chars(fmt); - - while (1) { - c = *(*fmt)++; - if (isalnum((int) c) || (c == '_')) - continue; - else - break; - } - (*fmt)--; - len = *fmt - start; - memcpy(buf, start, len); - buf[len] = 0; - - return buf; - - } /* pvariable */ - - static char *patom(const char **fmt, char *buf) - { - const char* start = *fmt; - char c; - int len; - - skip_null_chars(fmt); - - while (1) { - c = *(*fmt)++; - if (isalnum((int) c) || (c == '_') || (c == '@')) - continue; - else - break; - } - (*fmt)--; - len = *fmt - start; - memcpy(buf, start, len); - buf[len] = 0; - - return buf; - - } /* patom */ - - /* Check if integer or float - */ - static char *pdigit(const char **fmt, char *buf) - { - const char* start = *fmt; - char c; - int len,dotp=0; - - skip_null_chars(fmt); - - while (1) { - c = *(*fmt)++; - if (isdigit((int) c) || c == '-') - continue; - else if (!dotp && (c == '.')) { - dotp = 1; - continue; - } - else - break; - } - (*fmt)--; - len = *fmt - start; - memcpy(buf, start, len); - buf[len] = 0; - - return buf; - - } /* pdigit */ - - static char *pstring(const char **fmt, char *buf) - { - const char* start = ++(*fmt); /* skip first quote */ - char c; - int len; - - // skip_null_chars(fmt); - - while (1) { - c = *(*fmt)++; - if (c == '"') { - if (*((*fmt)-1) == '\\') - continue; - else - break; - } else - continue; - } - len = *fmt - 1 - start; /* skip last quote */ - memcpy(buf, start, len); - buf[len] = 0; - - return buf; - - } /* pstring */ - - static char *pquotedatom(const char **fmt, char *buf) - { - const char* start = ++(*fmt); /* skip first quote */ - char c; - int len; - - skip_null_chars(fmt); - - while (1) { - c = *(*fmt)++; - if (c == '\'') { - if (*((*fmt)-1) == '\\') - continue; - else - break; - } else - continue; - } - len = *fmt - 1 - start; /* skip last quote */ - memcpy(buf, start, len); - buf[len] = 0; - - return buf; - - } /* pquotedatom */ - - - /// @todo Implement support for parsing tail list expressions in the form - /// "[H | T]". Currently the pipe notation doesn't work. - /* - * The format letters are: - * a - An atom - * s - A string - * i - An integer - * l - A long integer - * u - An unsigned long integer - * f - A double float - * w - A pointer to some arbitrary term - */ - template - static int pformat(const char** fmt, va_list* pap, - vector& v, Alloc& a_alloc) - { - int rc=ERL_OK; - - /* this next section hacked to remove the va_arg calls */ - skip_null_chars(fmt); - - switch (*(*fmt)++) { - case 'w': - v.push_back(*va_arg(*pap, eterm*)); - break; - - case 'a': - v.push_back(eterm(atom(va_arg(*pap, char*)))); - break; - - case 's': - v.push_back(eterm(string(va_arg(*pap, char*), a_alloc))); - break; - - case 'i': - v.push_back(eterm(va_arg(*pap, int))); - break; - - case 'l': - v.push_back(eterm(va_arg(*pap, long))); - break; - - case 'u': - v.push_back(eterm((long)va_arg(*pap, unsigned long))); - break; - - case 'f': - v.push_back(eterm(va_arg(*pap, double))); - break; - - default: - rc = ERL_FMT_ERR; - break; - } - - return rc; - - } /* pformat */ - - template - static int ptuple(const char** fmt, va_list* pap, - vector& v, Alloc& a_alloc) - { - int res=ERL_FMT_ERR; - - skip_null_chars(fmt); - - switch (*(*fmt)++) { - - case '}': - res = ERL_OK; - break; - - case ',': - res = ptuple(fmt, pap, v, a_alloc); - break; - - default: { - (*fmt)--; - v.push_back(eformat(fmt, pap, a_alloc)); - if (v.back().type() != UNDEFINED) - res = ptuple(fmt, pap, v, a_alloc); - break; - - /* - if (isupper(**fmt)) { - v[size++] = erl_mk_var(pvariable(fmt, wbuf)); - res = ptuple(fmt, pap, v); - } - else if ((v[size++] = eformat(fmt, pap)) != (ErlTerm *) NULL) - res = ptuple(fmt, pap, v); - break; - */ - } - - } /* switch */ - - return res; - - } /* ptuple */ - - template - static int plist(const char** fmt, va_list* pap, - vector& v, Alloc& a_alloc) - { - int res=ERL_FMT_ERR; - - skip_null_chars(fmt); - - switch (*(*fmt)++) { - - case ']': - res = ERL_OK; - break; - - case ',': - res = plist(fmt, pap, v, a_alloc); - break; - - case '|': - skip_null_chars(fmt); - if (isupper((int)**fmt) || (**fmt == '_')) { - char wbuf[BUFSIZ]; - char* s = pvariable(fmt, wbuf); - v.push_back( eterm(var(s, a_alloc)) ); - skip_null_chars(fmt); - if (**fmt == ']') - res = ERL_OK; - break; - } - break; - - default: { - (*fmt)--; - eterm et = eformat(fmt, pap, a_alloc); - if (et.type() != UNDEFINED) { - v.push_back(et); - res = plist(fmt, pap, v, a_alloc); - } - break; - } - - } /* switch */ - - return res; - - } /* plist */ - - template - static eterm eformat(const char** fmt, va_list* pap, const Alloc& a_alloc) - throw (err_format_exception) - { - vector v(a_alloc); - Alloc alloc(a_alloc); - eterm ret; - - skip_null_chars(fmt); - - switch (*(*fmt)++) { - case '{': - if (ptuple(fmt, pap, v, alloc) != ERL_OK) - throw err_format_exception("Error parsing tuple", *fmt); - ret.set( eterm(tuple(&v[0], v.size(), alloc)) ); - break; - - case '[': - if (**fmt == ']') { - (*fmt)++; - ret.set( eterm(list(0, alloc)) ); - } else if (plist(fmt, pap, v, alloc) == ERL_OK) { - ret.set( eterm(list(&v[0], v.size(), alloc)) ); - } else { - throw err_format_exception("Error parsing list", *fmt); - } - break; - - case '$': /* char-value? */ - ret.set( eterm((int)(*(*fmt)++)) ); - break; - - case '~': - if (pformat(fmt, pap, v, alloc) != ERL_OK) - throw err_format_exception("Error parsing term", *fmt); - ret.set(v[0]); - break; - - default: { - char wbuf[BUFSIZ]; /* now local to this function for reentrancy */ - - (*fmt)--; - if (islower((int)**fmt)) { /* atom ? */ - char* a = patom(fmt, wbuf); - ret.set( eterm(atom(a)) ); - } else if (isupper((int)**fmt) || (**fmt == '_')) { - char* v = pvariable(fmt, wbuf); - ret.set( eterm(var(v, alloc)) ); - } else if (isdigit((int)**fmt) || **fmt == '-') { /* integer/float ? */ - char* digit = pdigit(fmt, wbuf); - if (strchr(digit,(int) '.') == NULL) - ret.set( eterm(atoi((const char *) digit)) ); - else - ret.set( eterm(atof((const char *) digit)) ); - } else if (**fmt == '"') { /* string ? */ - char* str = pstring(fmt, wbuf); - ret.set( eterm(string(str, alloc)) ); - } else if (**fmt == '\'') { /* quoted atom ? */ - char* qatom = pquotedatom(fmt, wbuf); - ret.set( eterm(atom(qatom)) ); - } - } - break; - } - - return ret; - - } /* eformat */ - - -} // namespace marshal -} // namespace EIXX_NAMESPACE - diff --git a/include/eixx/marshal/eterm_match.hpp b/include/eixx/marshal/eterm_match.hpp index 311ebd2..eeb6202 100644 --- a/include/eixx/marshal/eterm_match.hpp +++ b/include/eixx/marshal/eterm_match.hpp @@ -12,24 +12,19 @@ /* ***** BEGIN LICENSE BLOCK ***** -This file is part of the eixx (Erlang C++ Interface) Library. +Copyright 2010 Serge Aleynikov -Copyright (C) 2010 Serge Aleynikov -Copyright (C) 2005 Hector Rivas Gandara +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. + http://www.apache.org/licenses/LICENSE-2.0 -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. ***** END LICENSE BLOCK ***** */ @@ -40,8 +35,9 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include #include #include +#include -namespace EIXX_NAMESPACE { +namespace eixx { namespace marshal { // Forward declaration @@ -56,11 +52,12 @@ class eterm_pattern_action; */ template class eterm_pattern_matcher { - std::list, Alloc> m_pattern_list; + using ListAlloc = typename std::allocator_traits:: + template rebind_alloc>; public: - typedef std::list, Alloc> list_t; - typedef typename list_t::const_iterator const_iterator; - typedef typename list_t::iterator iterator; + using list_t = std::list, ListAlloc>; + using const_iterator = typename list_t::const_iterator; + using iterator = typename list_t::iterator; struct init_struct { eterm p; @@ -77,7 +74,7 @@ class eterm_pattern_matcher { * patterns in the match list shouldn't be checked, the * functor must return true. */ - typedef boost::function< + typedef std::function< bool (const eterm& a_pattern, const varbind& a_binding, long a_opaque) > pattern_functor_t; @@ -93,16 +90,23 @@ class eterm_pattern_matcher { * is the same as calling eterm_pattern_matcher() and iteratively * adding patterns using push_back() calls. */ - template + template eterm_pattern_matcher( const struct init_struct (&a_patterns)[N], pattern_functor_t a_fun, - const Alloc& a_alloc = Alloc() - ) + const Alloc& a_alloc = Alloc()) : m_pattern_list(a_alloc) { init(a_patterns, N, a_fun); } + eterm_pattern_matcher( + std::initializer_list> a_list, + const Alloc& a_alloc = Alloc()) + : m_pattern_list(a_alloc) + { + std::copy(a_list.begin(), a_list.end(), m_pattern_list.begin()); + } + /** * Initialize the pattern list from a given array of patterns. */ @@ -128,13 +132,25 @@ class eterm_pattern_matcher { return m_pattern_list.back(); } + const eterm_pattern_action& + push_back(const eterm& a_pattern) { + m_pattern_list.push_back(eterm_pattern_action(a_pattern)); + return m_pattern_list.back(); + } + /** * Add a pattern to the beginning of the list. * The pattern is assign to a smart pointer. */ const eterm_pattern_action& push_front(const eterm& a_pattern, pattern_functor_t a_fun, long a_opaque=0) { - m_pattern_list.push_back(eterm_pattern_action(a_pattern, a_fun, a_opaque)); + m_pattern_list.push_front(eterm_pattern_action(a_pattern, a_fun, a_opaque)); + return m_pattern_list.front(); + } + + const eterm_pattern_action& + push_front(const eterm& a_pattern) { + m_pattern_list.push_front(eterm_pattern_action(a_pattern)); return m_pattern_list.front(); } @@ -177,23 +193,19 @@ class eterm_pattern_matcher { * @param a_binding is an optional object containing * predefined variable bindings that will be passed * to every pattern. - * @return true if any one pattern matched the term. + * @return 0 if no terms matched, or the matched term's index + 1. */ - bool match(const eterm& a_term, - varbind* a_binding = NULL) const + int match(const eterm& a_term, + varbind* a_binding = NULL) const { - bool res = false; - for(typename std::list, Alloc>::const_iterator - it = m_pattern_list.begin(), end = m_pattern_list.end(); - it != end; ++it) - { - if (it->operator() (a_term, a_binding)) { - res = true; - break; - } - } - return res; + int i = 1; + for(auto it = m_pattern_list.begin(), end = m_pattern_list.end(); it != end; ++it, ++i) + if ((*it)(a_term, a_binding)) + return i; + return 0; } +private: + list_t m_pattern_list; }; /** @@ -205,10 +217,21 @@ class eterm_pattern_action { typedef typename eterm_pattern_matcher::pattern_functor_t pattern_functor_t; - eterm m_pattern; - pattern_functor_t m_fun; - long m_opaque; + eterm m_pattern; + pattern_functor_t m_fun; + long m_opaque; public: + /** + * Create a new pattern match action without a functor. + * @param a_pattern pattern to match + */ + explicit eterm_pattern_action(const eterm& a_pattern) + : m_pattern(a_pattern), m_opaque(0) + { + auto fun = [](auto& /*pattern*/, auto& /*vars*/, long /*opaque*/) { return true; }; + m_fun = fun; + } + /** * Create a new pattern match functor. * @param a_pattern pattern to match @@ -223,9 +246,55 @@ class eterm_pattern_action { BOOST_ASSERT(m_fun != NULL); } + template + eterm_pattern_action( + const eterm& a_pattern, const Lambda& a_fun, long a_opaque = 0) + : m_pattern(a_pattern), m_fun(a_fun), m_opaque(a_opaque) + { + BOOST_ASSERT(m_fun != NULL); + } + + eterm_pattern_action( + const Alloc& a_alloc, pattern_functor_t& a_fun, long a_opaque, + const char* a_pat_fmt, ...) + : m_fun(a_fun), m_opaque(a_opaque) + { + BOOST_ASSERT(m_fun != NULL); + va_list ap; + va_start(ap, a_pat_fmt); + try { m_pattern = eterm::format(a_alloc, &a_pat_fmt, &ap); } + catch (...) { va_end(ap); throw; } + va_end(ap); + } + + eterm_pattern_action(const eterm_pattern_action& a_rhs) + : m_pattern(a_rhs.m_pattern) + , m_fun(a_rhs.m_fun) + , m_opaque(a_rhs.m_opaque) + {} + + eterm_pattern_action(eterm_pattern_action&& a_rhs) + : m_pattern(std::move(a_rhs.m_pattern)) + , m_fun(std::move(a_rhs.m_fun)) + , m_opaque(a_rhs.m_opaque) + {} + + void operator=(eterm_pattern_action&& a_rhs) + { + m_pattern = std::move(a_rhs.m_pattern); + m_fun = std::move(a_rhs.m_fun); + m_opaque = a_rhs.m_opaque; + } + + void operator=(const eterm_pattern_action& a_rhs) + { + m_pattern = a_rhs.m_pattern; + m_fun = a_rhs.m_fun; + m_opaque = a_rhs.m_opaque; + } + bool operator() (const eterm& a_term, varbind* a_binding) const - throw (eterm_exception) { varbind binding; if (a_binding) @@ -245,6 +314,6 @@ class eterm_pattern_action { }; } // namespace marshal -} // namespace EIXX_NAMESPACE +} // namespace eixx #endif // _EI_MATCH_HPP_ diff --git a/include/eixx/marshal/list.hpp b/include/eixx/marshal/list.hpp index b85ee9d..8f7da01 100644 --- a/include/eixx/marshal/list.hpp +++ b/include/eixx/marshal/list.hpp @@ -10,35 +10,31 @@ /* ***** BEGIN LICENSE BLOCK ***** -This file is part of the eixx (Erlang C++ Interface) Library. +Copyright 2010 Serge Aleynikov -Copyright (C) 2010 Serge Aleynikov +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. + http://www.apache.org/licenses/LICENSE-2.0 -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. ***** END LICENSE BLOCK ***** */ -#ifndef _IMPL_LIST_HPP_ -#define _IMPL_LIST_HPP_ +#pragma once #include #include #include #include +#include -namespace EIXX_NAMESPACE { +namespace eixx { namespace marshal { template @@ -55,17 +51,39 @@ class list : protected alloc_base, Alloc> { typedef alloc_base base_t; struct header_t { + header_t() {} + header_t(std::nullptr_t) + : initialized(true) + , alloc_size (0) + , size (0) + , tail (nullptr) + {} bool initialized; - unsigned int alloc_size; - unsigned int size; + size_t alloc_size; + size_t size; cons_t* tail; + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wpedantic" cons_t head[0]; + #pragma GCC diagnostic pop }; typedef blob blob_t; blob_t* m_blob; + /// Returns a pointer to a singleton empty list + static blob_t* empty_list() { + auto creator = []() { + auto p = new blob_t(sizeof(header_t)); + auto h = reinterpret_cast(p->data()); + new (h) header_t(nullptr); + return p; + }; + static std::unique_ptr s_empty(creator()); + return s_empty.get(); + } + header_t* header() { BOOST_ASSERT(m_blob); return reinterpret_cast(m_blob->data()); } @@ -77,7 +95,7 @@ class list : protected alloc_base, Alloc> { cons_t* tail() { return header()->tail; } void release() { - if (!m_blob) + if (!m_blob || m_blob == empty_list()) return; if (m_blob->release(false)) { header_t* l_header = header(); @@ -103,9 +121,9 @@ class list : protected alloc_base, Alloc> { class iterator; typedef const iterator const_iterator; - iterator begin() { iterator it(empty() ? NULL : head()); return it; } - iterator end() { return iterator::end(); } - + iterator begin() { iterator it(empty() ? NULL : head()); return it; } + iterator end() { return iterator::end(); } + const_iterator begin() const { const_iterator it(empty() ? NULL : head()); return it; } const_iterator end() const { return iterator::end(); } @@ -114,41 +132,66 @@ class list : protected alloc_base, Alloc> { , m_blob(NULL) {} + /// Construct an NIL list (initialized list with no elements) + explicit list(std::nullptr_t) : m_blob(empty_list()) {} + + /// Construct a list with a given estimated size. + /// + /// When a_estimated_size is 0, an empty initialized list is created. Otherwise, + /// the list is not initialized. explicit list(int a_estimated_size, const Alloc& alloc = Alloc()) - : base_t(alloc) - , m_blob(new blob_t(sizeof(header_t) + a_estimated_size*sizeof(cons_t), alloc)) + : list((size_t)a_estimated_size, alloc) { - header_t* l_header = header(); - l_header->initialized = a_estimated_size == 0; - l_header->alloc_size = a_estimated_size; - l_header->size = 0; - l_header->tail = NULL; + if (a_estimated_size < 0) + throw err_bad_argument("List too short"); } - list(const list& a) - : base_t(a.get_allocator()), m_blob(a.m_blob) + /// Construct a list with a given estimated size. + /// + /// When a_estimated_size is 0, an empty initialized list is created. Otherwise, + /// the list is not initialized. + explicit list(size_t a_estimated_size, const Alloc& alloc = Alloc()) + : base_t(alloc) { + if (a_estimated_size == 0) + m_blob = empty_list(); + else { + m_blob = new blob_t(sizeof(header_t) + a_estimated_size*sizeof(cons_t), alloc); + header_t* hdr = header(); + hdr->initialized = a_estimated_size == 0; + hdr->alloc_size = a_estimated_size; + hdr->size = 0; + hdr->tail = NULL; + } + } + + list(const list& a) : base_t(a.get_allocator()), m_blob(a.m_blob) { BOOST_ASSERT(a.initialized()); if (m_blob) m_blob->inc_rc(); } - explicit list(const cons_t* a_head, int a_len = -1, const Alloc& alloc = Alloc()) - throw (err_bad_argument); + list(list&& a) : base_t(a.get_allocator()), m_blob(a.m_blob) { + a.m_blob = nullptr; + } + + explicit list(const cons_t* a_head, size_t a_len = 0, const Alloc& alloc = Alloc()); - template - list(const eterm (&items)[N], const Alloc& alloc = Alloc()); + template + list(const eterm (&items)[N], const Alloc& alloc = Alloc()) + : list(items, N, alloc) {} - list(const eterm items[], size_t a_size, const Alloc& alloc = Alloc()); + list(const eterm* items, size_t a_size, const Alloc& alloc = Alloc()) + : base_t(alloc) { init(items, a_size, alloc); } + + list(std::initializer_list> items, const Alloc& alloc = Alloc()) + : list(items.begin(), items.size(), alloc) {} /** * Decode the list from a binary buffer. */ - explicit list(const char* buf, int& idx, size_t size, const Alloc& a_alloc = Alloc()) - throw(err_decode_exception); + explicit list(const char* buf, uintptr_t& idx, size_t size, const Alloc& a_alloc = Alloc()); - ~list() { - release(); - } + ~list() { release(); } /** * Add a term to list. Tuples added must be fully filled @@ -166,25 +209,36 @@ class list : protected alloc_base, Alloc> { * Closes the list. * A list must be closed before it can be copied or included into other terms. */ - void close() { header()->initialized = true; } + void close() { + if (!m_blob || m_blob == empty_list()) return; + header()->initialized = true; + } /// Return list length. This method has O(1) complexity. - size_t length() const { return m_blob ? header()->size : 0; } + size_t length() const { return m_blob ? header()->size : 0; } bool empty() const { return !m_blob || header()->size == 0; } - bool initialized() const { return m_blob && header()->initialized; } + bool initialized() const { return m_blob && header()->initialized; } /// Return pointer to the N'th element in the list. This method has /// O(N) complexity. - const eterm& nth(size_t n) const throw(err_bad_argument) { + const eterm& nth(size_t n) const { if (n > length()) throw err_bad_argument("Index out of bounds", n); - const_iterator it = begin(), endit = end(); size_t i = 0; - for(; it != endit, i < n; ++it, ++i); + auto it = begin(); + for(auto endit = end(); it != endit && i < n; ++it, ++i); return *it; } - list tail(size_t idx) const throw(err_bad_argument); + list tail(size_t idx) const; + + list& operator= (const list& rhs) { + BOOST_ASSERT(rhs.initialized()); + release(); + m_blob = rhs.m_blob; + if (m_blob) m_blob->inc_rc(); + return *this; + } bool operator== (const list& rhs) const { const_iterator it1 = begin(), it2 = rhs.begin(), @@ -196,6 +250,16 @@ class list : protected alloc_base, Alloc> { return it1 == end1 && it2 == end2; } + bool operator< (const list& rhs) const { + const_iterator it1 = begin(), it2 = rhs.begin(), + end1 = end(), end2 = rhs.end(); + for(; it1 != end1 && it2 != end2; ++it1, ++it2) { + if (!(*it1 < *it2)) + return false; + } + return it1 == end1 && it2 != end2; + } + size_t encode_size() const { if (length() == 0) return 1; @@ -209,14 +273,12 @@ class list : protected alloc_base, Alloc> { return result; } - void encode(char* buf, int& idx, size_t size) const; + void encode(char* buf, uintptr_t& idx, size_t size) const; + + bool subst(eterm& out, const varbind* binding) const; - bool subst(eterm& out, const varbind* binding) const - throw (err_unbound_variable); + bool match(const eterm& pattern, varbind* binding) const; - bool match(const eterm& pattern, varbind* binding) const - throw (err_invalid_term, err_unbound_variable); - std::ostream& dump(std::ostream& out, const varbind* vars = NULL) const; static list make(const Alloc& a = Alloc()) { @@ -326,17 +388,15 @@ class list::iterator { }; } // namespace marshal -} // namespace EIXX_NAMESPACE +} // namespace eixx namespace std { template - ostream& operator<< (ostream& out, const EIXX_NAMESPACE::marshal::list& a) { + ostream& operator<< (ostream& out, const eixx::marshal::list& a) { return a.dump(out); } } // namespace std -#include - -#endif // _IMPL_LIST_HPP_ +#include diff --git a/include/eixx/marshal/list.ipp b/include/eixx/marshal/list.hxx similarity index 71% rename from include/eixx/marshal/list.ipp rename to include/eixx/marshal/list.hxx index 716cbb4..5084fd6 100644 --- a/include/eixx/marshal/list.ipp +++ b/include/eixx/marshal/list.hxx @@ -9,23 +9,19 @@ /* ***** BEGIN LICENSE BLOCK ***** -This file is part of the eixx (Erlang C++ Interface) Library. +Copyright 2010 Serge Aleynikov -Copyright (C) 2010 Serge Aleynikov +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. + http://www.apache.org/licenses/LICENSE-2.0 -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. ***** END LICENSE BLOCK ***** */ @@ -37,24 +33,11 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include #include -namespace EIXX_NAMESPACE { +namespace eixx { namespace marshal { template -template -inline list::list(const eterm (&items)[N], const Alloc& alloc) - : base_t(alloc) { - init(items, N, alloc); -} - -template -inline list::list(const eterm items[], size_t N, const Alloc& alloc) - : base_t(alloc) { - init(items, N, alloc); -} - -template -void list::init(const eterm items[], size_t N, const Alloc& alloc) { +void list::init(const eterm* items, size_t N, const Alloc& alloc) { size_t n = N > 0 ? N : 1; m_blob = new blob_t(sizeof(header_t) + n*sizeof(cons_t), alloc); @@ -65,35 +48,39 @@ void list::init(const eterm items[], size_t N, const Alloc& alloc) l_header->alloc_size = n; l_header->size = N; - for (size_t i=0; i < N; i++) { - BOOST_ASSERT(items[i].initialized()); - new (&hd[i].node) eterm(items[i]); - hd[i].next = &hd[i+1]; + for(auto p = items, end = items+N; p != end; ++p, ++hd) { + BOOST_ASSERT(p->initialized()); + new (&hd->node) eterm(*p); + hd->next = hd+1; } if (N == 0) l_header->tail = NULL; else { - l_header->tail = &hd[n-1]; - hd[N-1].next = NULL; + l_header->tail = hd-1; + l_header->tail->next = NULL; } } template -list::list(const cons_t* a_head, int a_len, const Alloc& alloc) - throw (err_bad_argument) : base_t(alloc) +list::list(const cons_t* a_head, size_t a_len, const Alloc& alloc) + : base_t(alloc) { - unsigned int alloc_size; + size_t alloc_size; - if (a_len >= 0) { + if (a_len > 0) { alloc_size = a_len; - } else if (a_len == -1) { - a_len = 0; + } else { for (const cons_t* p = a_head; p; p = p->next) a_len++; alloc_size = a_len; - } else - throw err_bad_argument("List of negative length!"); + } + + // If this is an empty list - no allocation is needed + if (alloc_size == 0) { + m_blob = empty_list(); + return; + } m_blob = new blob_t(sizeof(header_t) + alloc_size*sizeof(cons_t), alloc); header_t* l_header = header(); @@ -114,12 +101,21 @@ list::list(const cons_t* a_head, int a_len, const Alloc& alloc) } template -list::list(const char *buf, int& idx, size_t size, const Alloc& a_alloc) - throw(err_decode_exception) : base_t(a_alloc) +list::list(const char *buf, uintptr_t& idx, size_t size, const Alloc& a_alloc) + : base_t(a_alloc) { - int arity; - if (ei_decode_list_header(buf, &idx, &arity) < 0) + BOOST_ASSERT(idx <= INT_MAX); + int n; + if (ei_decode_list_header(buf, (int*)&idx, &n) < 0) err_decode_exception("Error decoding list header", idx); + + size_t arity = static_cast(n); + // If this is an empty list - no allocation is needed + if (arity == 0) { + m_blob = empty_list(); + return; + } + m_blob = new blob_t(sizeof(header_t) + arity*sizeof(cons_t), a_alloc); header_t* l_header = header(); l_header->initialized = true; @@ -127,15 +123,15 @@ list::list(const char *buf, int& idx, size_t size, const Alloc& a_alloc) l_header->size = arity; cons_t* hd = l_header->head; - for (int i=0; i < arity; i++) { + for (cons_t* end = hd+arity; hd != end; ++hd) { eterm et(buf, idx, size, a_alloc); - new (&hd[i].node) eterm(et); - hd[i].next = &hd[i+1]; + new (&hd->node) eterm(et); + hd->next = hd+1; } if (arity == 0) { l_header->tail = NULL; } else { - l_header->tail = &hd[arity-1]; + l_header->tail = hd-1; l_header->tail->next = NULL; if (*(buf+idx) != ERL_NIL_EXT) throw err_decode_exception("Not a NIL list!", idx); @@ -145,7 +141,7 @@ list::list(const char *buf, int& idx, size_t size, const Alloc& a_alloc) } template -void list::encode(char* buf, int& idx, size_t size) const +void list::encode(char* buf, uintptr_t& idx, size_t size) const { BOOST_ASSERT(initialized()); char* s = buf + idx; @@ -154,7 +150,11 @@ void list::encode(char* buf, int& idx, size_t size) const } else { put8(s,ERL_LIST_EXT); const header_t* l_header = header(); - put32be(s,l_header->size); + auto sz = l_header->size; + if (sz > UINT32_MAX) + throw err_encode_exception("LIST_EXT length exceeds maximum"); + uint32_t len = (uint32_t)sz; + put32be(s, len); idx += 5; for(const cons_t* p = l_header->head; p; p = p->next) { visit_eterm_encoder visitor(buf, idx, size); @@ -168,12 +168,12 @@ void list::encode(char* buf, int& idx, size_t size) const } template -list list::tail(size_t idx) const throw(err_bad_argument) +list list::tail(size_t idx) const { const header_t* l_header = header(); const cons_t* p = l_header->head; - int len = (int)l_header->size - (int)idx - 1; - if (len < 0) + size_t len = l_header->size - idx - 1; + if (idx >= l_header->size) throw err_bad_argument("List too short"); for (size_t i=0; i <= idx; i++) p = p->next; @@ -193,7 +193,7 @@ void list::push_back(const eterm& a) hd->size = 1; hd->alloc_size = 1; cons_t* p = hd->tail; - p->node.set(a); + new (&p->node) eterm(a); p->next = NULL; return; } @@ -211,7 +211,6 @@ void list::push_back(const eterm& a) template bool list::subst(eterm& out, const varbind* binding) const - throw (err_unbound_variable) { // We check if any contained term changes. bool changed = false; @@ -245,7 +244,6 @@ bool list::subst(eterm& out, const varbind* binding) const template bool list::match(const eterm& pattern, varbind* binding) const - throw (err_invalid_term, err_unbound_variable) { switch (pattern.type()) { case VAR: return pattern.match(eterm(*this), binding); @@ -286,4 +284,4 @@ std::ostream& list::dump(std::ostream& out, const varbind* vars) c } } // namespace marshal -} // namespace EIXX_NAMESPACE +} // namespace eixx diff --git a/include/eixx/marshal/map.hpp b/include/eixx/marshal/map.hpp new file mode 100644 index 0000000..23894ac --- /dev/null +++ b/include/eixx/marshal/map.hpp @@ -0,0 +1,249 @@ +//---------------------------------------------------------------------------- +/// \file map.hpp +//---------------------------------------------------------------------------- +/// \brief A class implementing an map object of Erlang external term format. +//---------------------------------------------------------------------------- +// Copyright (c) 2020 Serge Aleynikov +// Created: 2020-06-10 +//---------------------------------------------------------------------------- +/* +***** BEGIN LICENSE BLOCK ***** + +Copyright 2010 Serge Aleynikov + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +***** END LICENSE BLOCK ***** +*/ +#ifndef _IMPL_MAP_HPP_ +#define _IMPL_MAP_HPP_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace eixx { +namespace marshal { + +template class eterm; + +template +class map { +public: + using MapAlloc = typename std::allocator_traits::template rebind_alloc, eterm>>; + using MapT = std::map, eterm, std::less>, MapAlloc>; +protected: + using BlobT = blob; + + BlobT* m_blob; + + void release() { release(m_blob); m_blob = nullptr; } + + void release(BlobT* p) { + if (p && p->release(false)) { + p->data()->~MapT(); + p->free(); + } + } + + void initialize(const Alloc& alloc = Alloc()) { + m_blob = new BlobT(1, alloc); + auto* m = m_blob->data(); + new (m) MapT(); + } +public: + using const_iterator = typename MapT::const_iterator; + using iterator = typename MapT::iterator; + + static const map& null() { static map s = map(Alloc()); return s; } + + explicit map(std::nullptr_t) : m_blob(nullptr) {} + + explicit map(const Alloc& a = Alloc()) { + static_assert(sizeof(map) == sizeof(void*), "Invalid class size!"); + initialize(a); + } + + map(const map& s) : m_blob(s.m_blob) { + if (m_blob) m_blob->inc_rc(); + } + + map(map&& s) : m_blob(s.m_blob) { + s.m_blob = nullptr; + } + + map(std::initializer_list,eterm>> items, const Alloc& alloc = Alloc()) { + initialize(alloc); + auto* m = m_blob->data(); + for (auto& pair : items) + m->insert(pair); + } + + map(const char* buf, uintptr_t& idx, size_t size, const Alloc& a_alloc = Alloc()) { + BOOST_ASSERT(idx <= INT_MAX); + int arity; + if (ei_decode_map_header(buf, (int*)&idx, &arity) < 0) + err_decode_exception("Error decoding tuple header", idx); + initialize(a_alloc); + auto* m = m_blob->data(); + for (int i=0; i < arity; i++) { + auto key = eterm(buf, idx, size, a_alloc); + auto val = eterm(buf, idx, size, a_alloc); + m->emplace(std::make_pair(key, val)); + } + BOOST_ASSERT((size_t)idx <= size); + } + + ~map() { + release(); + } + + map& operator= (const map& s) { + if (this != &s) { + release(); + m_blob = s.m_blob; + if (m_blob) m_blob->inc_rc(); + } + return *this; + } + + map& operator= (map&& s) { + if (this != &s) { + release(); + m_blob = s.m_blob; + s.m_blob = nullptr; + } + return *this; + } + + const eterm& operator[](const eterm& key) const { + static const eterm s_undefined = am_undefined; + if (!m_blob) return s_undefined; + auto it = m_blob->data()->find(key); + return (it == m_blob->data()->end()) ? s_undefined : it->second; + } + + void insert(const eterm& key, const eterm& val) { + if (!m_blob) initialize(); + m_blob->data()->insert(std::make_pair(key, val)); + } + + void erase(const eterm& key) { + if (!m_blob) return; + m_blob->data()->erase(key); + } + + const_iterator begin() const { return m_blob ? m_blob->data()->cbegin() : null().begin(); } + const_iterator end() const { return m_blob ? m_blob->data()->cend() : null().end(); } + + size_t size() const { return m_blob ? m_blob->data()->size() : 0; } + bool empty() const { return !m_blob || m_blob->data()->empty(); } + + void clear() { if (m_blob) m_blob->data().clear(); } + + // Use only for debugging + int use_count() const { return m_blob ? m_blob->use_count() : -1000000; } + + bool operator== (const map& rhs) const { + if (size() != rhs.size()) return false; + auto it1 = begin(); + auto it2 = rhs.begin(); + auto end1 = end(); + for (; it1 != end1; ++it1, ++it2) + if (!(*it1 == *it2)) + return false; + return true; + } + + bool operator< (const map& rhs) const { + if (size() < rhs.size()) return true; + if (size() > rhs.size()) return false; + BOOST_ASSERT(size() == rhs.size()); + auto it1 = begin(); + auto it2 = rhs.begin(); + auto end1 = end(); + for (; it1 != end1; ++it1, ++it2) { + // 1. Is key1 < key2? + if (it1->first < it2->first) + return true; + if (it2->first < it1->first) + return false; + // 1. Is value1 < value2? + if (it1->second < it2->second) + return true; + if (it2->second < it1->second) + return false; + } + return size() == 0; + } + + /** Size of buffer needed to hold the encoded map. */ + size_t encode_size() const { + BOOST_ASSERT(m_blob); + size_t result = 5; + for (const_iterator it = begin(), iend = end(); it != iend; ++it) { + result += visit_eterm_encode_size_calc().apply_visitor(it->first); // key + result += visit_eterm_encode_size_calc().apply_visitor(it->second); // value + } + return result; + } + + void encode(char* buf, uintptr_t& idx, size_t size) const { + BOOST_ASSERT(m_blob); + BOOST_ASSERT(idx <= INT_MAX); + size_t arity = this->size(); + if (arity > INT_MAX) + throw err_encode_exception("MAP_EXT arity exceeds maximum supported"); + ei_encode_map_header(buf, (int*)&idx, (int)arity); + for(auto it = begin(), iend=end(); it != iend; ++it) { + visit_eterm_encoder key_visitor(buf, idx, size); + key_visitor.apply_visitor(it->first); + visit_eterm_encoder val_visitor(buf, idx, size); + val_visitor.apply_visitor(it->second); + } + BOOST_ASSERT((size_t)idx <= size); + } + + std::ostream& dump(std::ostream& out, const varbind* binding=NULL) const { + out << "#{"; + for(auto it = begin(), first=begin(), iend=end(); it != iend; ++it) { + out << (it != first ? "," : ""); + visit_eterm_stringify key_visitor(out, binding); + key_visitor.apply_visitor(it->first); + out << " => "; + visit_eterm_stringify val_visitor(out, binding); + val_visitor.apply_visitor(it->second); + } + return out << '}'; + } +}; + +} // namespace marshal +} // namespace eixx + +namespace std { + + template + ostream& operator<< (ostream& out, const eixx::marshal::map& m) { return m.dump(out); } + +} + +//#include + +#endif // _IMPL_MAP_HPP_ diff --git a/include/eixx/marshal/pid.hpp b/include/eixx/marshal/pid.hpp index 92cebc2..c497891 100644 --- a/include/eixx/marshal/pid.hpp +++ b/include/eixx/marshal/pid.hpp @@ -10,23 +10,19 @@ /* ***** BEGIN LICENSE BLOCK ***** -This file is part of the eixx (Erlang C++ Interface) Library. +Copyright 2010 Serge Aleynikov -Copyright (C) 2010 Serge Aleynikov +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. + http://www.apache.org/licenses/LICENSE-2.0 -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. ***** END LICENSE BLOCK ***** */ @@ -35,10 +31,12 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #define _IMPL_PID_HPP_ #include +#include #include #include +#include -namespace EIXX_NAMESPACE { +namespace eixx { namespace marshal { /** @@ -51,27 +49,22 @@ class epid { struct pid_blob { // creation is a special value that allows // distinguishing pid values between successive node restarts. - union u { - struct s { - uint8_t creation: 3; - uint16_t serial : 13; - uint16_t id : 15; - } s; - uint32_t i; - - BOOST_STATIC_ASSERT(sizeof(s) == 4); - - u(uint16_t a_id, uint16_t a_ser, uint8_t a_cre) { - s.id = a_id & 0x7fff; s.serial = a_ser & 0x1fff; s.creation = a_cre & 0x03; - } - } u; - atom node; - - pid_blob(const atom& a_node, uint16_t a_id, uint16_t a_ser, uint8_t a_cre) - : u(a_id, a_ser, a_cre), node(a_node) + uint32_t id; // only 15 bits may be used and the rest must be 0 + uint32_t serial; // only 13 bits may be used and the rest must be 0 + uint32_t creation; + atom node; + + pid_blob(const atom& a_node, uint32_t a_id, uint32_t a_cre) + : id(a_id), serial(0), creation(a_cre), node(a_node) + {} + + pid_blob(const atom& a_node, uint32_t a_id, uint32_t a_serial, uint32_t a_cre) + : id(a_id), serial(a_serial), creation(a_cre), node(a_node) {} }; + BOOST_STATIC_ASSERT(sizeof(pid_blob) == sizeof(uint64_t)*2); + blob* m_blob; void release() { @@ -86,8 +79,7 @@ class epid { } // Must only be called from constructor! - void init(const atom& node, uint16_t id, uint16_t serial, uint8_t creation, - const Alloc& alloc) throw(err_bad_argument) + void init(const atom& node, uint32_t id, uint32_t serial, uint32_t creation, const Alloc& alloc) { m_blob = new blob(1, alloc); new (m_blob->data()) pid_blob(node, id, serial, creation); @@ -97,13 +89,19 @@ class epid { #endif } - void decode(const char* buf, int& idx, size_t size, const Alloc& a_alloc) - throw (err_decode_exception, err_bad_argument); - - epid() {} + /// @throw err_decode_exception + /// @throw err_bad_argument + void decode(const char* buf, uintptr_t& idx, size_t size, const Alloc& a_alloc); public: + static const epid null; + + /// When true - include 'Creation' in printing to string/stream + static bool display_creation() { return config::display_creation(); } + + epid() : m_blob(nullptr) {} + /** * Create an Erlang pid from its components using provided allocator. * @param node the nodename. @@ -116,76 +114,86 @@ class epid { * @param a_alloc is the allocator to use. * @throw err_bad_argument if node is empty or greater than MAX_NODE_LENGTH **/ - epid(const char* node, int id, int serial, int creation, const Alloc& a_alloc = Alloc()) - throw(err_bad_argument) - { - int len = strlen(node); - detail::check_node_length(len); - atom l_node(node, len); - init(l_node, id, serial, creation, a_alloc); - } + epid(const char* node, uint32_t id, uint32_t serial, uint32_t creation, const Alloc& a_alloc = Alloc()) + : epid(atom(node), id, serial, creation, a_alloc) + {} - epid(const atom& node, int id, int serial, int creation, const Alloc& a_alloc = Alloc()) - throw(err_bad_argument) + epid(const atom& node, uint32_t id, uint32_t creation, const Alloc& a_alloc = Alloc()) + : epid(node, id, 0, creation, a_alloc) + {} + + epid(const atom& node, uint32_t id, uint32_t serial, uint32_t creation, const Alloc& a_alloc = Alloc()) { detail::check_node_length(node.size()); init(node, id, serial, creation, a_alloc); } /// Decode the pid from a binary buffer. - epid(const char* buf, int& idx, size_t size, const Alloc& a_alloc = Alloc()) - throw (err_decode_exception, err_bad_argument) { + epid(const char* buf, uintptr_t& idx, size_t size, const Alloc& a_alloc = Alloc()) { decode(buf, idx, size, a_alloc); } epid(const epid& rhs) : m_blob(rhs.m_blob) { - m_blob->inc_rc(); - #ifdef EIXX_DEBUG - std::cerr << "Copied pid " << *this - << " [addr=" << this << ", blob=" << m_blob - << ", rc=" << m_blob->use_count() << ']' << std::endl; - #endif + if (m_blob) { + m_blob->inc_rc(); + #ifdef EIXX_DEBUG + std::cerr << "Copied pid " << *this + << " [addr=" << this << ", blob=" << m_blob + << ", rc=" << m_blob->use_count() << ']' << std::endl; + #endif + } } + epid(epid&& rhs) : m_blob(rhs.m_blob) { rhs.m_blob = nullptr; } + ~epid() { release(); } - void operator= (const epid& rhs) { - release(); m_blob = rhs.m_blob; m_blob->inc_rc(); - #ifdef EIXX_DEBUG - std::cerr << "Copied pid " << *this - << " [addr=" << this << ", blob=" << m_blob - << ", rc=" << m_blob->use_count() << ']' << std::endl; - #endif + epid& operator= (const epid& rhs) { + if (this != &rhs) { + release(); + m_blob = rhs.m_blob; + if (!m_blob) m_blob->inc_rc(); + } + return *this; + } + + epid& operator= (epid&& rhs) { + if (this != &rhs) { + release(); + m_blob = rhs.m_blob; + rhs.m_blob = nullptr; + } + return *this; } /** * Get the node name from the PID. * @return the node name from the PID. **/ - const atom& node() const { return m_blob->data()->node; } + atom node() const { return m_blob ? m_blob->data()->node : atom::null(); } /** * Get the id number from the PID. * @return the id number from the PID. **/ - int id() const { return m_blob->data()->u.s.id; } + uint32_t id() const { return m_blob ? m_blob->data()->id : 0; } /** * Get the serial number from the PID. * @return the serial number from the PID. **/ - int serial() const { return m_blob->data()->u.s.serial; } + uint32_t serial() const { return m_blob ? m_blob->data()->serial : 0; } /** * Get the creation number from the PID. * @return the creation number from the PID. **/ - int creation() const { return m_blob->data()->u.s.creation; } + uint32_t creation() const { return m_blob ? m_blob->data()->creation : 0; } - uint32_t id_internal() const { return m_blob->data()->u.i & 0x7FFFFFFF; } + uint32_t id_internal() const { return m_blob ? m_blob->data()->id : 0; } bool operator== (const epid& rhs) const { - return id_internal() == rhs.id_internal() && node() == rhs.node(); + return ::memcmp(m_blob->data(), rhs.m_blob->data(), sizeof(pid_blob)) == 0; } bool operator!= (const epid& rhs) const { return !(*this == rhs); } @@ -196,31 +204,44 @@ class epid { if (n > 0) return false; if (id_internal() < t2.id_internal()) return true; if (id_internal() > t2.id_internal()) return false; + if (serial() < t2.serial()) return true; + if (creation() > t2.creation()) return false; + if (creation() < t2.creation()) return true; return false; } - size_t encode_size() const { return 13 + node().size(); } + size_t encode_size() const { + return + #ifndef ERL_NEW_PID_EXT + 13 + #elif defined(ERL_PID_EXT) + 16 + #endif + + node().size(); + } - void encode(char* buf, int& idx, size_t size) const; + void encode(char* buf, uintptr_t& idx, size_t size) const; - std::ostream& dump(std::ostream& out, const varbind* binding=NULL) const { - return out << "#Pid<" << node() - << '.' << id() << '.' << serial() << '.' << creation() << ">"; + std::ostream& dump(std::ostream& out, const varbind* =NULL) const { + out << "#Pid<" << node() + << '.' << id() << '.' << serial(); + if (creation() > 0 && display_creation()) + out << ',' << creation(); + return out << '>'; } - }; } // namespace marshal -} // namespace EIXX_NAMESPACE +} // namespace eixx namespace std { template - ostream& operator<< (ostream& out, const EIXX_NAMESPACE::marshal::epid& a) { + ostream& operator<< (ostream& out, const eixx::marshal::epid& a) { return a.dump(out); } } // namespace std -#include +#include #endif // _IMPL_PID_HPP_ diff --git a/include/eixx/marshal/pid.hxx b/include/eixx/marshal/pid.hxx new file mode 100644 index 0000000..d90ae43 --- /dev/null +++ b/include/eixx/marshal/pid.hxx @@ -0,0 +1,108 @@ +//---------------------------------------------------------------------------- +/// \file pid.hxx +//---------------------------------------------------------------------------- +/// \brief Implementation of the epid member functions. +//---------------------------------------------------------------------------- +// Copyright (c) 2010 Serge Aleynikov +// Created: 2010-09-20 +//---------------------------------------------------------------------------- +/* +***** BEGIN LICENSE BLOCK ***** + +Copyright 2010 Serge Aleynikov + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +***** END LICENSE BLOCK ***** +*/ + +#include +#include + +namespace eixx { +namespace marshal { + +template +void epid::decode(const char *buf, uintptr_t& idx, [[maybe_unused]] size_t size, const Alloc& alloc) +{ + const char* s = buf + idx; + const char* s0 = s; + uint8_t tag = get8(s); + if (tag != ERL_PID_EXT +#ifdef ERL_NEW_PID_EXT + && tag != ERL_NEW_PID_EXT +#endif + ) + throw err_decode_exception("Error decoding pid's type", idx, tag); + + const uint8_t atom_tag = get8(s); + long l = atom::get_len(s, atom_tag); + if (l < 0) + throw err_decode_exception("Error decoding pid's node", idx, l); + size_t len = static_cast(l); + detail::check_node_length(len); + + atom l_node(s, len); + s += len; + + uint32_t l_id = get32be(s); /* 15 bits if distribution flag DFLAG_V4_NC is not set */ + uint32_t l_ser = get32be(s); /* 13 bits if distribution flag DFLAG_V4_NC is not set */ +#ifdef ERL_NEW_PID_EXT + uint32_t l_cre = tag == ERL_NEW_PID_EXT ? get32be(s) : (get8(s) & 0x03); +#else + uint32_t l_cre = get8(s) & 0x03; /* 2 bits */ +#endif + + init(l_node, l_id, l_ser, l_cre, alloc); + + idx += static_cast(s - s0); + BOOST_ASSERT((size_t)idx <= size); +} + +template +void epid::encode(char* buf, uintptr_t& idx, [[maybe_unused]] size_t size) const +{ + char* s = buf + idx; + char* s0 = s; + s++; // Skip ERL_PID_EXT + + /* the nodename */ +#ifdef ERL_ATOM_UTF8_EXT + put8(s, ERL_ATOM_UTF8_EXT); +#else + put8(s, ERL_ATOM_EXT); +#endif + atom nd = node(); + uint16_t n = nd.size(); + put16be(s, n); + memmove(s, nd.c_str(), n); + s += n; + + /* the integers */ + put32be(s, id()); /* 15 bits if distribution flag DFLAG_V4_NC is not set */ + put32be(s, serial()); /* 13 bits if distribution flag DFLAG_V4_NC is not set */ + const uint32_t l_cre = creation(); +#ifdef ERL_NEW_PID_EXT + *s0 = ERL_NEW_PID_EXT; + put32be(s, l_cre); +#else + *s0 = ERL_PID_EXT; + put8(s, l_cre & 0x03 /* 2 bits */); +#endif + + idx += static_cast(s - s0); + BOOST_ASSERT((size_t)idx <= size); +} + +} // namespace marshal +} // namespace eixx diff --git a/include/eixx/marshal/pid.ipp b/include/eixx/marshal/pid.ipp deleted file mode 100644 index c621631..0000000 --- a/include/eixx/marshal/pid.ipp +++ /dev/null @@ -1,87 +0,0 @@ -//---------------------------------------------------------------------------- -/// \file pid.ipp -//---------------------------------------------------------------------------- -/// \brief Implementation of the epid member functions. -//---------------------------------------------------------------------------- -// Copyright (c) 2010 Serge Aleynikov -// Created: 2010-09-20 -//---------------------------------------------------------------------------- -/* -***** BEGIN LICENSE BLOCK ***** - -This file is part of the eixx (Erlang C++ Interface) Library. - -Copyright (C) 2010 Serge Aleynikov - -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -***** END LICENSE BLOCK ***** -*/ - -#include -#include - -namespace EIXX_NAMESPACE { -namespace marshal { - -template -void epid::decode(const char *buf, int& idx, size_t size, const Alloc& alloc) - throw(err_decode_exception, err_bad_argument) -{ - const char* s = buf + idx; - const char* s0 = s; - if (get8(s) != ERL_PID_EXT || get8(s) != ERL_ATOM_EXT) - throw err_decode_exception("Error decoding pid", -1); - - int len = get16be(s); - detail::check_node_length(len); - - atom l_node(s, len); - s += len; - - int l_id = get32be(s); - int l_serial = get32be(s); - int l_creation = get8(s); - - init(l_node, l_id, l_serial, l_creation, alloc); - - idx += s - s0; - BOOST_ASSERT((size_t)idx <= size); -} - -template -void epid::encode(char* buf, int& idx, size_t size) const -{ - char* s = buf + idx; - char* s0 = s; - put8(s,ERL_PID_EXT); - put8(s,ERL_ATOM_EXT); - const atom::string_t& nd = node().to_string(); - unsigned short n = nd.size(); - put16be(s, n); - memmove(s, nd.c_str(), n); - s += n; - - /* now the integers */ - put32be(s, id() & 0x7fff); /* 15 bits */ - put32be(s, serial() & 0x1fff); /* 13 bits */ - put8(s, (creation() & 0x03)); /* 2 bits */ - - idx += s-s0; - BOOST_ASSERT((size_t)idx <= size); -} - -} // namespace marshal -} // namespace EIXX_NAMESPACE diff --git a/include/eixx/marshal/port.hpp b/include/eixx/marshal/port.hpp index eae3188..744ebba 100644 --- a/include/eixx/marshal/port.hpp +++ b/include/eixx/marshal/port.hpp @@ -10,23 +10,19 @@ /* ***** BEGIN LICENSE BLOCK ***** -This file is part of the eixx (Erlang C++ Interface) Library. +Copyright 2010 Serge Aleynikov -Copyright (C) 2010 Serge Aleynikov +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. + http://www.apache.org/licenses/LICENSE-2.0 -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. ***** END LICENSE BLOCK ***** */ @@ -37,8 +33,9 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include #include #include +#include -namespace EIXX_NAMESPACE { +namespace eixx { namespace marshal { /** @@ -49,11 +46,11 @@ namespace marshal { template class port { struct port_blob { - uint8_t creation; - int id; - atom node; + uint32_t creation; + uint64_t id; + atom node; - port_blob(const atom& a_node, int a_id, uint8_t a_cre) + port_blob(const atom& a_node, uint64_t a_id, uint32_t a_cre) : creation(a_cre), id(a_id), node(a_node) {} }; @@ -66,15 +63,21 @@ class port { } // Must only be called from constructor! - void init(const atom& node, int id, uint8_t creation, - const Alloc& alloc) throw(err_bad_argument) + void init(const atom& node, uint64_t id, uint32_t creation, + const Alloc& alloc) { m_blob = new blob(1, alloc); - new (m_blob->data()) port_blob(node, id & 0x0fffffff, creation & 0x03); + new (m_blob->data()) port_blob(node, id, creation); } - port() {} public: + static const port null; + + /// When true - include 'Creation' in printing to string/stream + static bool display_creation() { return config::display_creation(); } + + port() : m_blob(nullptr) {} + /** * Create an Erlang port from its components. * If node string size is greater than MAX_NODE_LENGTH or = 0, @@ -86,8 +89,7 @@ class port { * 2 bits will be used. * @throw err_bad_argument if node is empty or greater than MAX_NODE_LENGTH **/ - port(const char* node, const int id, const int creation, const Alloc& a_alloc = Alloc()) - throw (err_bad_argument) + port(const char* node, const uint64_t id, const uint32_t creation, const Alloc& a_alloc = Alloc()) { size_t n = strlen(node); detail::check_node_length(n); @@ -95,8 +97,7 @@ class port { init(l_node, id, creation, a_alloc); } - port(const atom& node, const int id, const int creation, const Alloc& a_alloc = Alloc()) - throw (err_bad_argument) + port(const atom& node, const uint64_t id, const uint32_t creation, const Alloc& a_alloc = Alloc()) { detail::check_node_length(node.size()); init(node, id, creation, a_alloc); @@ -110,32 +111,45 @@ class port { * @param size is the size of the \a buf buffer. * @param a_alloc is the allocator to use. */ - port(const char *buf, int& idx, size_t size, const Alloc& a_alloc = Alloc()) - throw (err_decode_exception); + port(const char *buf, uintptr_t& idx, size_t size, const Alloc& a_alloc = Alloc()); - port(const port& rhs) : m_blob(rhs.m_blob) { m_blob->inc_rc(); } + port(const port& rhs) : m_blob(rhs.m_blob) { if (m_blob) m_blob->inc_rc(); } + port(port&& rhs) : m_blob(rhs.m_blob) { rhs.m_blob = nullptr; } ~port() { release(); } - void operator= (const port& rhs) { release(); m_blob = rhs.m_blob; m_blob->inc_rc(); } + port& operator= (const port& rhs) { + if (this != &rhs) { + release(); m_blob = rhs.m_blob; + if (m_blob) m_blob->inc_rc(); + } + return *this; + } + + port& operator= (port&& rhs) { + if (this != &rhs) { + release(); m_blob = rhs.m_blob; rhs.m_blob = nullptr; + } + return *this; + } /** * Get the node name from the PORT. * @return the node name from the PORT. **/ - const atom& node() const { return m_blob->data()->node; } + atom node() const { return m_blob ? m_blob->data()->node : atom::null(); } /** * Get the id number from the PORT. * @return the id number from the PORT. **/ - int id() const { return m_blob->data()->id; } + uint64_t id() const { return m_blob ? m_blob->data()->id : 0; } /** * Get the creation number from the PORT. * @return the creation number from the PORT. **/ - int creation() const { return m_blob->data()->creation; } + uint32_t creation() const { return m_blob ? m_blob->data()->creation : 0; } bool operator== (const port& t) const { return id() == t.id() && node() == t.node() && creation() == t.creation(); @@ -153,27 +167,38 @@ class port { return false; } - size_t encode_size() const { return 9 + node().size(); } + size_t encode_size() const { + return id() > 0x0fffffff ? 16 : + #if defined(ERL_V4_PORT_EXT) || defined(ERL_NEW_PORT_EXT) + 12 + #else + 9 + #endif + + node().size(); + } - void encode(char* buf, int& idx, size_t size) const; + void encode(char* buf, uintptr_t& idx, size_t size) const; - std::ostream& dump(std::ostream& out, const varbind* binding=NULL) const { + std::ostream& dump(std::ostream& out, const varbind* =NULL) const { return out << *this; } }; } // namespace marshal -} // namespace EIXX_NAMESPACE +} // namespace eixx namespace std { template - ostream& operator<< (ostream& out, const EIXX_NAMESPACE::marshal::port& a) { - return out << "#Port<" << a.node() << "." << a.id() << ">"; + ostream& operator<< (ostream& out, const eixx::marshal::port& a) { + out << "#Port<" << a.node() << "." << a.id(); + if (a.creation() > 0 && a.display_creation()) + out << ',' << a.creation(); + return out << '>'; } } // namespace std -#include +#include #endif // _IMPL_PORT_HPP_ diff --git a/include/eixx/marshal/port.hxx b/include/eixx/marshal/port.hxx new file mode 100644 index 0000000..f2fd41b --- /dev/null +++ b/include/eixx/marshal/port.hxx @@ -0,0 +1,135 @@ +//---------------------------------------------------------------------------- +/// \file port.hxx +//---------------------------------------------------------------------------- +/// \brief Implemention of port member functions. +//---------------------------------------------------------------------------- +// Copyright (c) 2010 Serge Aleynikov +// Created: 2010-09-20 +//---------------------------------------------------------------------------- +/* +***** BEGIN LICENSE BLOCK ***** + +Copyright 2010 Serge Aleynikov + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +***** END LICENSE BLOCK ***** +*/ + +#include +#include + +namespace eixx { +namespace marshal { + +template +port::port(const char *buf, uintptr_t& idx, [[maybe_unused]] size_t size, const Alloc& a_alloc) +{ + const char* s = buf + idx; + const char* s0 = s; + uint8_t tag = get8(s); + if (tag != ERL_PORT_EXT +#ifdef ERL_NEW_PORT_EXT + && tag != ERL_NEW_PORT_EXT +#endif +#ifdef ERL_V4_PORT_EXT + && tag != ERL_V4_PORT_EXT +#endif + ) + throw err_decode_exception("Error decoding port's type", idx, tag); + + const uint8_t atom_tag = get8(s); + long l = atom::get_len(s, atom_tag); + if (l < 0) + throw err_decode_exception("Error decoding port's node", idx, l); + size_t len = static_cast(l); + detail::check_node_length(len); + atom l_node(s, len); + s += len; + + uint64_t id; + uint32_t cre; + + switch (tag) { +#ifdef ERL_V4_PORT_EXT + case ERL_V4_PORT_EXT: + id = get64be(s); + cre = get32be(s); + break; +#endif +#ifdef ERL_NEW_PORT_EXT + case ERL_NEW_PORT_EXT: + id = static_cast(get32be(s)); + cre = get32be(s); + break; +#endif + case ERL_PORT_EXT: + id = static_cast(get32be(s) & 0x0fffffff); /* 28 bits */ + cre = static_cast(get8(s) & 0x03); /* 2 bits */ + break; + + default: + throw err_decode_exception("Error decoding port's type", idx, tag); + } + init(l_node, id, cre, a_alloc); + + idx += static_cast(s - s0); + BOOST_ASSERT((size_t)idx <= size); +} + +template +void port::encode(char* buf, uintptr_t& idx, [[maybe_unused]] size_t size) const +{ + char* s = buf + idx; + char* s0 = s; + s++; // Skip ERL_PORT_EXT + + /* the nodename */ +#ifdef ERL_ATOM_UTF8_EXT + put8(s, ERL_ATOM_UTF8_EXT); +#else + put8(s, ERL_ATOM_EXT); +#endif + atom nd = node(); + uint16_t n = nd.size(); + put16be(s, n); + memmove(s, nd.c_str(), n); + s += n; + + /* the integers */ + const uint32_t l_cre = creation(); +#if defined(ERL_V4_PORT_EXT) + if (id() > 0x0fffffff /* 28 bits */) { + *s0 = ERL_V4_PORT_EXT; + put64be(s, id()); + put32be(s, l_cre); + } else { +#elif defined(ERL_NEW_PORT_EXT) + *s0 = ERL_NEW_PORT_EXT; + put32be(s, id() & 0x0fffffff /* 28 bits */); + put32be(s, l_cre); +#else + *s0 = ERL_PORT_EXT; + put32be(s, id() & 0x0fffffff /* 28 bits */); + put8(s, l_cre & 0x03 /* 2 bits */); +#endif +#if defined(ERL_V4_PORT_EXT) + } +#endif + + idx += static_cast(s - s0); + BOOST_ASSERT((size_t)idx <= size); +} + +} // namespace marshal +} // namespace eixx diff --git a/include/eixx/marshal/port.ipp b/include/eixx/marshal/port.ipp deleted file mode 100644 index 6c02a9d..0000000 --- a/include/eixx/marshal/port.ipp +++ /dev/null @@ -1,82 +0,0 @@ -//---------------------------------------------------------------------------- -/// \file port.ipp -//---------------------------------------------------------------------------- -/// \brief Implemention of port member functions. -//---------------------------------------------------------------------------- -// Copyright (c) 2010 Serge Aleynikov -// Created: 2010-09-20 -//---------------------------------------------------------------------------- -/* -***** BEGIN LICENSE BLOCK ***** - -This file is part of the eixx (Erlang C++ Interface) Library. - -Copyright (C) 2010 Serge Aleynikov - -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -***** END LICENSE BLOCK ***** -*/ - -#include -#include - -namespace EIXX_NAMESPACE { -namespace marshal { - -template -port::port(const char *buf, int& idx, size_t size, const Alloc& a_alloc) - throw(err_decode_exception) -{ - const char* s = buf + idx; - const char* s0 = s; - if (get8(s) != ERL_PORT_EXT || get8(s) != ERL_ATOM_EXT) - throw err_decode_exception("Error decoding port", -1); - int len = get16be(s); - detail::check_node_length(len); - atom l_node(s, len); - s += len; - - int l_id = get32be(s) & 0x0fffffff; /* 28 bits */ - uint8_t l_cre = get8(s) & 0x03; /* 2 bits */ - init(l_node, l_id, l_cre, a_alloc); - - idx += s - s0; - BOOST_ASSERT((size_t)idx <= size); -} - -template -void port::encode(char* buf, int& idx, size_t size) const -{ - char* s = buf + idx; - char* s0 = s; - put8(s,ERL_PORT_EXT); - put8(s,ERL_ATOM_EXT); - const atom::string_t& str = node().to_string(); - unsigned short n = str.size(); - put16be(s, n); - memmove(s, str.c_str(), n); - s += n; - - /* now the integers */ - put32be(s, id() & 0x0fffffff); /* 28 bits */ - put8(s, (creation() & 0x03)); /* 2 bits */ - - idx += s-s0; - BOOST_ASSERT((size_t)idx <= size); -} - -} // namespace marshal -} // namespace EIXX_NAMESPACE diff --git a/include/eixx/marshal/ref.hpp b/include/eixx/marshal/ref.hpp index d411792..074a806 100644 --- a/include/eixx/marshal/ref.hpp +++ b/include/eixx/marshal/ref.hpp @@ -10,23 +10,19 @@ /* ***** BEGIN LICENSE BLOCK ***** -This file is part of the eixx (Erlang C++ Interface) Library. +Copyright 2010 Serge Aleynikov -Copyright (C) 2010 Serge Aleynikov +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. + http://www.apache.org/licenses/LICENSE-2.0 -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. ***** END LICENSE BLOCK ***** */ @@ -35,10 +31,16 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #define _IMPL_REF_HPP_ #include +#include +#include +#include +#include #include -namespace EIXX_NAMESPACE { +namespace eixx { namespace marshal { +namespace detail { +} // namespace detail /** * Representation of erlang Pids. @@ -46,30 +48,48 @@ namespace marshal { */ template class ref { - enum { COUNT = 3 }; + enum { COUNT = 5 /* max of 5 when the DFLAG_V4_NC has been set */ }; struct ref_blob { - uint8_t creation; atom node; + uint16_t len; uint32_t ids[COUNT]; + uint32_t creation; - ref_blob(const atom& a_node, uint32_t* a_ids, uint8_t a_cre) - : creation(a_cre), node(a_node) + ref_blob(const atom& a_node, const uint32_t* a_ids, uint16_t n, uint32_t a_cre) + : node(a_node) + , len(n) + , creation(a_cre) { - ids[0] = a_ids[0] & 0x3ffff; - ids[1] = a_ids[1]; - ids[2] = a_ids[2]; + assert(n >= 3 && n <= COUNT); + + int i = 0; + for (auto p = a_ids, e = a_ids+std::min(COUNT,n); p != e; ++p) + ids[i++] = *p; + + while(i < COUNT) + ids[i++] = 0; } + template + ref_blob(const atom& a_node, uint32_t (&a_ids)[N], uint32_t a_cre) + : ref_blob(a_node, a_ids, N, a_cre) + {} + + ref_blob(const atom& a_node, std::initializer_list a_ids, uint32_t a_cre) + : ref_blob(a_node, &*a_ids.begin(), a_ids.size(), a_cre) + {} }; blob* m_blob; // Must only be called from constructor! - void init(const atom& node, uint32_t* ids, int creation, - const Alloc& alloc) throw(err_bad_argument) + void init(const atom& a_node, const uint32_t* a_ids, uint16_t n, uint32_t a_cre, + const Alloc& alloc) { + detail::check_node_length(a_node.size()); + m_blob = new blob(1, alloc); - new (m_blob->data()) ref_blob(node, ids, creation); + new (m_blob->data()) ref_blob(a_node, a_ids, n, a_cre); } void release() { @@ -77,8 +97,22 @@ class ref { m_blob->release(); } - ref() {} + uint32_t id0() const { return m_blob->data()->ids[0]; } + uint64_t id1() const { return m_blob->data()->ids[1]; } + public: + inline static const uint32_t* s_ref_ids() { + static const uint32_t s_ref_ids[] = {0, 0, 0, 0, 0}; + return s_ref_ids; + } + + static const ref null; + + /// When true - include 'Creation' in printing to string/stream + static bool display_creation() { return config::display_creation(); } + + ref() : m_blob(nullptr) {} + /** * Create an Erlang ref from its components. * If node string size is greater than MAX_NODE_LENGTH or = 0, @@ -90,31 +124,32 @@ class ref { * 2 bits will be used. * @throw err_bad_argument if node is empty or greater than MAX_NODE_LENGTH */ - template - ref(const char* node, uint32_t (&ids)[N], unsigned int creation, - const Alloc& a_alloc = Alloc()) throw(err_bad_argument) + ref(const atom& node, const uint32_t* a_ids, uint16_t n, uint32_t creation, + const Alloc& a_alloc = Alloc()) { - BOOST_STATIC_ASSERT(N == 3); - int len = strlen(node); - detail::check_node_length(len); - atom l_node(node, len); - init(l_node, ids, creation, a_alloc); + init(node, a_ids, n, creation, a_alloc); } - template - ref(const atom& node, uint32_t (&ids)[N], unsigned int creation, - const Alloc& a_alloc = Alloc()) throw(err_bad_argument) + template + ref(const atom& node, uint32_t (&ids)[N], uint32_t creation, + const Alloc& a_alloc = Alloc()) + : ref(node, ids, N, creation, a_alloc) { - detail::check_node_length(node.size()); - init(node, ids, creation, a_alloc); + BOOST_STATIC_ASSERT(N >= 3 && N <= 5); } - ref(const atom& node, uint32_t id1, uint32_t id2, uint32_t id3, unsigned int creation, - const Alloc& a_alloc = Alloc()) throw(err_bad_argument) + ref(const atom& node, uint32_t id0, uint32_t id1, uint32_t id2, uint32_t creation, + const Alloc& a_alloc = Alloc()) + : ref(node, {id0, id1, id2}, creation, a_alloc) + {} + + // For internal use + ref(const atom& node, std::initializer_list a_ids, uint32_t creation, + const Alloc& a_alloc = Alloc()) + : ref(node, &*a_ids.begin(), (uint16_t)a_ids.size(), creation, a_alloc) { - detail::check_node_length(node.size()); - uint32_t ids[] = { id1, id2, id3 }; - init(node, ids, creation, a_alloc); + if (a_ids.size() > UINT16_MAX) + throw err_bad_argument("Ref too long"); } /** @@ -125,20 +160,33 @@ class ref { * @param size is the size of the \a buf buffer. * @param a_alloc is the allocator to use. */ - ref(const char* buf, int& idx, size_t size, const Alloc& a_alloc = Alloc()) - throw(err_decode_exception); + ref(const char* buf, uintptr_t& idx, size_t size, const Alloc& a_alloc = Alloc()); - ref(const ref& rhs) : m_blob(rhs.m_blob) { m_blob->inc_rc(); } + ref(const ref& rhs) : m_blob(rhs.m_blob) { if (m_blob) m_blob->inc_rc(); } + ref(ref&& rhs) : m_blob(rhs.m_blob) { rhs.m_blob = nullptr; } ~ref() { release(); } - void operator= (const ref& rhs) { release(); m_blob = rhs.m_blob; m_blob->inc_rc(); } + ref& operator= (const ref& rhs) { + if (this != &rhs) { + release(); m_blob = rhs.m_blob; + if (m_blob) m_blob->inc_rc(); + } + return *this; + } + + ref& operator= (ref&& rhs) { + if (this != &rhs) { + release(); m_blob = rhs.m_blob; rhs.m_blob = nullptr; + } + return *this; + } /** * Get the node name from the REF. * @return the node name from the REF. */ - const atom& node() const { return m_blob->data()->node; } + atom node() const { return m_blob ? m_blob->data()->node : atom::null(); } /** * Get an id number from the REF. @@ -146,7 +194,7 @@ class ref { * @return the id number from the REF. */ uint32_t id(uint32_t index) const { - BOOST_ASSERT(index < COUNT); + BOOST_ASSERT(index < len()); return ids()[index]; } @@ -154,46 +202,62 @@ class ref { * Get the id array from the REF. * @return the id array number from the REF. */ - const uint32_t* ids() const { return m_blob->data()->ids; } + const uint32_t* ids() const { return m_blob ? m_blob->data()->ids : s_ref_ids(); } + + /** + * Get the id array from the REF. + * @return the id array number from the REF. + */ + uint16_t len() const { return m_blob ? m_blob->data()->len : 0; } /** * Get the creation number from the REF. * @return the creation number from the REF. */ - int creation() const { return m_blob->data()->creation; } + uint32_t creation() const { return m_blob ? m_blob->data()->creation : 0; } bool operator==(const ref& t) const { - return node() == t.node() && - memcmp(ids(), t.ids(), COUNT*sizeof(uint32_t)) == 0 && - creation() == t.creation(); + return ::memcmp(m_blob->data(), t.m_blob->data(), sizeof(ref_blob)) == 0; } /// Less operator, needed for maps bool operator<(const ref& rhs) const { + if (!rhs.m_blob) return m_blob; + if (!m_blob) return true; int n = node().compare(rhs.node()); - if (n < 0) return true; - if (n > 0) return false; - if (id(0) < rhs.id(0)) return true; - if (id(0) > rhs.id(0)) return false; - if (id(1) < rhs.id(1)) return true; - if (id(1) > rhs.id(1)) return false; - if (id(2) < rhs.id(2)) return true; - if (id(2) > rhs.id(2)) return false; + if (n != 0) return n < 0; + auto e = std::min(len(), rhs.len()); + for (uint32_t i=0; i < e; ++i) { + auto i1 = id(i); + auto i2 = rhs.id(i); + if (i1 > i2) return false; + if (i1 < i2) return true; + } + if (len() < rhs.len()) return true; + if (len() > rhs.len()) return false; + if (creation() < rhs.creation()) return true; + if (creation() > rhs.creation()) return false; return false; } - size_t encode_size() const - { return 1+2+(3+node().size()) + COUNT*4 + 1; } + size_t encode_size() const { + return (size_t)1+2+(3+node().size()) + len()*4 + + #ifdef ERL_NEWER_REFERENCE_EXT + 4; + #else + 1; + #endif + } - void encode(char* buf, int& idx, size_t size) const; + void encode(char* buf, uintptr_t& idx, size_t size) const; - std::ostream& dump(std::ostream& out, const varbind* binding=NULL) const { + std::ostream& dump(std::ostream& out, const varbind* =nullptr) const { return out << *this; } }; } //namespace marshal -} //namespace EIXX_NAMESPACE +} //namespace eixx namespace std { /** @@ -201,14 +265,18 @@ namespace std { * as \verbatim #Ref \endverbatim **/ template - ostream& operator<< (ostream& out, const EIXX_NAMESPACE::marshal::ref& a) { - return out << "#Ref<" << a.node() << '.' - << a.id(2) << '.' << a.id(1) << '.' << a.id(0) << '>'; + ostream& operator<< (ostream& out, const eixx::marshal::ref& a) { + out << "#Ref<" << a.node(); + for (uint32_t i=0, e=a.len(); i != e; ++i) + out << '.' << a.id(i); + if (a.creation() > 0 && a.display_creation()) + out << ',' << a.creation(); + return out << '>'; } } // namespace std -#include +#include #endif // _IMPL_REF_HPP_ diff --git a/include/eixx/marshal/ref.hxx b/include/eixx/marshal/ref.hxx new file mode 100644 index 0000000..5215ea4 --- /dev/null +++ b/include/eixx/marshal/ref.hxx @@ -0,0 +1,142 @@ +//---------------------------------------------------------------------------- +/// \file ref.hxx +//---------------------------------------------------------------------------- +/// \brief Implementation of ref member functions. +//---------------------------------------------------------------------------- +// Copyright (c) 2010 Serge Aleynikov +// Created: 2010-09-20 +//---------------------------------------------------------------------------- +/* +***** BEGIN LICENSE BLOCK ***** + +Copyright 2010 Serge Aleynikov + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +***** END LICENSE BLOCK ***** +*/ + +#include +#include +#include +#include + +namespace eixx { +namespace marshal { + +template +ref::ref(const char* buf, uintptr_t& idx, [[maybe_unused]] size_t size, const Alloc& a_alloc) +{ + const char *s = buf + idx; + const char *s0 = s; + uint8_t tag = get8(s); + + switch (tag) { +#ifdef ERL_NEWER_REFERENCE_EXT + case ERL_NEWER_REFERENCE_EXT: +#endif +#ifdef ERL_NEW_REFERENCE_EXT + case ERL_NEW_REFERENCE_EXT: { + uint16_t count = get16be(s); // First goes the count + if (count > COUNT) + throw err_decode_exception("Error decoding ref's count", idx+1, count); + + const uint8_t atom_tag = get8(s); + long l = atom::get_len(s, atom_tag); + if (l < 0) + throw err_decode_exception("Error decoding ref's node", idx, l); + size_t len = static_cast(l); + detail::check_node_length(len); + atom nd(s, len); + s += len; + + uint32_t cre, mask; + std::tie(cre, mask) = tag == ERL_NEW_REFERENCE_EXT + ? std::make_pair((get8(s) & 0x03u), 0x0003ffffu) /* 18 bits */ + : std::make_pair(uint32_t(get32be(s)), 0xFFFFffff); + + uint32_t vals[COUNT]; + for (auto p=vals, e=p+count; p != e; ++p) + *p = get32be(s) & mask; + + init(nd, vals, count, cre, a_alloc); + + idx += static_cast(s - s0); + break; + } +#endif + case ERL_REFERENCE_EXT: { + const uint8_t atom_tag = get8(s); + long l = atom::get_len(s, atom_tag); + if (l < 0) + throw err_decode_exception("Error decoding ref's node", idx, l); + size_t len = static_cast(l); + detail::check_node_length(len); + atom nd(s, len); + s += len; + + uint32_t id = get32be(s) & 0x0003ffff; /* 18 bits */ + uint32_t cre = get8(s) & 0x03; /* 2 bits */ + + init(nd, &id, 1u, cre, a_alloc); + + idx += static_cast(s - s0); + break; + } + default: + throw err_decode_exception("Error decoding ref's type", idx, tag); + } +} + +template +void ref::encode(char* buf, uintptr_t& idx, [[maybe_unused]] size_t size) const +{ + char* s = buf + idx; + char* s0 = s; + s++; // Skip ERL_NEW_REFERENCE_EXT + + /* first, count of ids */ + put16be(s, len()); + + /* the nodename */ +#ifdef ERL_ATOM_UTF8_EXT + put8(s, ERL_ATOM_UTF8_EXT); +#else + put8(s, ERL_ATOM_EXT); +#endif + atom nd = node(); + uint16_t n = nd.size(); + put16be(s, n); + memmove(s, nd.c_str(), n); + s += n; + + /* the integers */ + const uint32_t l_cre = creation(); +#ifdef ERL_NEWER_REFERENCE_EXT + *s0 = ERL_NEWER_REFERENCE_EXT; + put32be(s, l_cre); + for (auto* p = ids(), *e = p + len(); p != e; ++p) + put32be(s, *p); +#else + *s0 = ERL_NEW_REFERENCE_EXT; + put8(s, l_cre & 0x03 /* 2 bits */); + for (auto* p = ids(), *e = p + len(); p != e; ++p) + put32be(s, *p & 0x0003ffff /* 18 bits */); +#endif + + idx += static_cast(s - s0); + BOOST_ASSERT((size_t)idx <= size); +} + +} // namespace marshal +} // namespace eixx diff --git a/include/eixx/marshal/ref.ipp b/include/eixx/marshal/ref.ipp deleted file mode 100644 index 45f0592..0000000 --- a/include/eixx/marshal/ref.ipp +++ /dev/null @@ -1,104 +0,0 @@ -//---------------------------------------------------------------------------- -/// \file ref.ipp -//---------------------------------------------------------------------------- -/// \brief Implementation of ref member functions. -//---------------------------------------------------------------------------- -// Copyright (c) 2010 Serge Aleynikov -// Created: 2010-09-20 -//---------------------------------------------------------------------------- -/* -***** BEGIN LICENSE BLOCK ***** - -This file is part of the eixx (Erlang C++ Interface) Library. - -Copyright (C) 2010 Serge Aleynikov - -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -***** END LICENSE BLOCK ***** -*/ - -#include -#include -#include -#include - -namespace EIXX_NAMESPACE { -namespace marshal { - -template -ref::ref(const char* buf, int& idx, size_t size, const Alloc& a_alloc) - throw(err_decode_exception) -{ - const char *s = buf + idx; - const char *s0 = s; - int type = get8(s); - - switch (type) { - case ERL_NEW_REFERENCE_EXT: { - int count = get16be(s); - if (count != COUNT) - throw err_decode_exception("Error decoding ref's count", idx+1); - if (get8(s) != ERL_ATOM_EXT) - throw err_decode_exception("Error decoding ref's atom", idx+3); - - int len = get16be(s); - detail::check_node_length(len); - atom l_node(s, len); - s += len; - - uint8_t l_creation = get8(s) & 0x03; - uint32_t l_ids[COUNT]; - - for (size_t i = 0; i < COUNT; i++) - l_ids[i] = get32be(s); - - init(l_node, l_ids, l_creation, a_alloc); - - idx += s-s0; - break; - } - default: - throw err_decode_exception("Error decoding ref's type", type); - } -} - -template -void ref::encode(char* buf, int& idx, size_t size) const -{ - char* s = buf + idx; - char* s0 = s; - put8(s,ERL_NEW_REFERENCE_EXT); - /* first, number of integers */ - put16be(s, COUNT); - /* then the nodename */ - put8(s,ERL_ATOM_EXT); - const atom::string_t& str = node().to_string(); - unsigned short n = str.size(); - put16be(s, n); - memmove(s, str.c_str(), n); - s += n; - - /* now the integers */ - put8(s, (creation() & 0x03)); /* 2 bits */ - for (size_t i=0; i < COUNT; ++i) - put32be(s, ids()[i]); - - idx += s-s0; - BOOST_ASSERT((size_t)idx <= size); -} - -} // namespace marshal -} // namespace EIXX_NAMESPACE diff --git a/include/eixx/marshal/string.hpp b/include/eixx/marshal/string.hpp index 7a19372..c325ffb 100644 --- a/include/eixx/marshal/string.hpp +++ b/include/eixx/marshal/string.hpp @@ -10,23 +10,19 @@ /* ***** BEGIN LICENSE BLOCK ***** -This file is part of the eixx (Erlang C++ Interface) Library. +Copyright 2010 Serge Aleynikov -Copyright (C) 2010 Serge Aleynikov +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. + http://www.apache.org/licenses/LICENSE-2.0 -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. ***** END LICENSE BLOCK ***** */ @@ -41,7 +37,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include #include -namespace EIXX_NAMESPACE { +namespace eixx { namespace marshal { template class varbind; @@ -60,12 +56,20 @@ class string public: typedef const char* const_iterator; - string() : m_blob(NULL) {} + static const string& null() { static string s; return s; } + + string() : m_blob(nullptr) {} + + string(size_t a_sz, const Alloc& a = Alloc()) + : m_blob(new blob(a_sz+1, a)) + { + m_blob->data()[m_blob->size()-1] = '\0'; + } string(const char* s, const Alloc& a = Alloc()) { BOOST_ASSERT(s); if (!s[0]) { - m_blob = NULL; + m_blob = nullptr; return; } m_blob = new blob(strlen(s)+1, a); @@ -74,7 +78,7 @@ class string } string(const std::string& s, const Alloc& a = Alloc()) { if (s.empty()) { - m_blob = NULL; + m_blob = nullptr; return; } m_blob = new blob(s.size()+1, a); @@ -83,28 +87,46 @@ class string } string(const char* s, size_t n, const Alloc& a = Alloc()) { if (n == 0) { - m_blob = NULL; + m_blob = nullptr; return; } m_blob = new blob(n+1, a); - memcpy(m_blob->data(), s, m_blob->size()); - m_blob->data()[m_blob->size()-1] = '\0'; + if (s != NULL) { + memcpy(m_blob->data(), s, m_blob->size()); + m_blob->data()[m_blob->size()-1] = '\0'; + } else + m_blob->data()[0] = '\0'; } string(const string& s) : m_blob(s.m_blob) { if (m_blob) m_blob->inc_rc(); } - string(const char* buf, int& idx, size_t size, const Alloc& a_alloc = Alloc()) - throw(err_decode_exception); + string(string&& s) : m_blob(s.m_blob) { + s.m_blob = nullptr; + } + + string(const char* buf, uintptr_t& idx, size_t size, const Alloc& a_alloc = Alloc()); ~string() { release(); } - void operator= (const string& s) { - release(); - m_blob = s.m_blob; - if (m_blob) m_blob->inc_rc(); + string& operator= (const string& s) { + if (this != &s) { + release(); + m_blob = s.m_blob; + if (m_blob) m_blob->inc_rc(); + } + return *this; + } + + string& operator= (string&& s) { + if (this != &s) { + release(); + m_blob = s.m_blob; + s.m_blob = nullptr; + } + return *this; } void operator= (const std::string& s) { @@ -121,6 +143,7 @@ class string const char* c_str() const { return m_blob ? m_blob->data() : ""; } size_t size() const { return m_blob ? m_blob->size()-1 : 0; } + std::string to_str() const { return m_blob ? std::string(m_blob->data(), m_blob->size()-1) : ""; } size_t length() const { return size(); } bool empty() const { return c_str()[0] == '\0'; } @@ -140,17 +163,19 @@ class string } /// Tests if this string is equal to the content of the binary buffer \a rhs. - template + template bool equal(const char (&rhs)[N]) const { int i = N > 0 && rhs[N-1] == '\0' ? -1 : 0; return strncmp(c_str(), rhs, size()+i) == 0; } /// Tests if this string is equal to the content of the binary buffer \a rhs. - template + template bool equal(const uint8_t (&rhs)[N]) const { - int i = N > 0 && rhs[N-1] == '\0' ? -1 : 0; - return strncmp(c_str(), (const char*)rhs, size()+i) == 0; + size_t sz = size(); + if (sz > 0 && N > 0 && rhs[N-1] == '\0') + sz -= 1; + return strncmp(c_str(), (const char*)rhs, sz) == 0; } /** Size of binary buffer needed to hold the encoded string. */ @@ -159,31 +184,41 @@ class string return n == 0 ? 1 : n + (n <= 0xffff ? 3 : 5+n+1); } - void encode(char* buf, int& idx, size_t size) const { - ei_encode_string_len(buf, &idx, c_str(), length()); + void encode(char* buf, uintptr_t& idx, size_t) const { + BOOST_ASSERT(idx <= INT_MAX); + size_t len = size(); + if (len > INT_MAX) + throw err_encode_exception("STRING_EXT length exceeds maximum"); + ei_encode_string_len(buf, (int*)&idx, c_str(), (int)len); + } + + template + Stream& to_binary_string(Stream& out) { + return eixx::to_binary_string(out, c_str(), size()); } - std::ostream& dump(std::ostream& out, const varbind* binding=NULL) const { + std::string to_binary_string() const { return eixx::to_binary_string(c_str(), size()); } + + std::ostream& dump(std::ostream& out, const varbind* =NULL) const { return out << *this; } }; template static std::string to_binary_string(const string& a) { - return to_binary_string(a.c_str(), a.size()); + return a.to_binary_string(); } template -string::string(const char* buf, int& idx, size_t size, const Alloc& a_alloc) - throw(err_decode_exception) +string::string(const char* buf, uintptr_t& idx, [[maybe_unused]] size_t size, const Alloc& a_alloc) { - const char *s = buf + idx; + const char *s = buf + idx; const char *s0 = s; - int etype = get8(s); + uint8_t tag = get8(s); - switch (etype) { + switch (tag) { case ERL_STRING_EXT: { - int len = get16be(s); + uint16_t len = get16be(s); if (len == 0) m_blob = NULL; else { @@ -200,15 +235,15 @@ string::string(const char* buf, int& idx, size_t size, const Alloc& a_all * but we decode as much as we can, exiting early if we run into a * non-character in the list. */ - int len = get32be(s); + uint32_t len = get32be(s); if (len == 0) m_blob = NULL; else { m_blob = new blob(len+1, a_alloc); - for (int i=0; idata()[i] = get8(s); + for (uint32_t i=0; i(s - s0)+i); + m_blob->data()[i] = static_cast(get8(s)); } m_blob->data()[len] = '\0'; } @@ -219,33 +254,33 @@ string::string(const char* buf, int& idx, size_t size, const Alloc& a_all break; default: - throw err_decode_exception("Error decoding string type", etype); + throw err_decode_exception("Error decoding string's type", idx, tag); } - idx += s-s0; + idx += static_cast(s - s0); } } // namespace marshal -} // namespace EIXX_NAMESPACE +} // namespace eixx namespace std { template - ostream& operator<< (ostream& out, const EIXX_NAMESPACE::marshal::string& s) { + ostream& operator<< (ostream& out, const eixx::marshal::string& s) { return out << '"' << s.c_str() << '"'; } template - bool operator== (const std::string& lhs, const EIXX_NAMESPACE::marshal::string& rhs) { + bool operator== (const std::string& lhs, const eixx::marshal::string& rhs) { return lhs == rhs.c_str(); } template - bool operator== (const EIXX_NAMESPACE::marshal::string& lhs, const std::string& rhs) { - return rhs == rhs.c_str(); + bool operator== (const eixx::marshal::string& lhs, const std::string& rhs) { + return lhs == rhs.c_str(); } template - bool operator== (const EIXX_NAMESPACE::marshal::string& lhs, const char* rhs) { + bool operator== (const eixx::marshal::string& lhs, const char* rhs) { return strcmp(rhs, lhs.c_str(), lhs.size()) == 0; } diff --git a/include/eixx/marshal/trace.hpp b/include/eixx/marshal/trace.hpp index fa76120..11008cb 100644 --- a/include/eixx/marshal/trace.hpp +++ b/include/eixx/marshal/trace.hpp @@ -12,23 +12,19 @@ /* ***** BEGIN LICENSE BLOCK ***** -This file is part of the eixx (Erlang C++ Interface) Library. +Copyright 2010 Serge Aleynikov -Copyright (C) 2010 Serge Aleynikov +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. + http://www.apache.org/licenses/LICENSE-2.0 -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. ***** END LICENSE BLOCK ***** */ @@ -41,7 +37,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include #include -namespace EIXX_NAMESPACE { +namespace eixx { namespace marshal { /// Represents trace operations performed on the trace::tracer. @@ -59,7 +55,7 @@ class trace : protected tuple { /// Increment the serial number. This method is not atomic. void inc_serial() { long n = serial(); (*this)[4] = n; (*this)[2] = n+1; } - void check_clock(uint32_t& clock) { + void check_clock(long& clock) { long n = serial(); if (n > clock) (*this)[4] = clock = n; } @@ -81,8 +77,7 @@ class trace : protected tuple { } /// Decode the pid from a binary buffer. - trace(const char* buf, int& idx, size_t a_size, const Alloc& a_alloc = Alloc()) - throw (err_decode_exception) + trace(const char* buf, uintptr_t& idx, size_t a_size, const Alloc& a_alloc = Alloc()) : tuple(buf, idx, a_size, a_alloc) { if (size() != 5 || (*this)[0].type() != LONG @@ -90,7 +85,7 @@ class trace : protected tuple { || (*this)[2].type() != LONG || (*this)[3].type() != PID || (*this)[4].type() != LONG) - throw err_decode_exception("Invalid trace token type!"); + throw err_decode_exception("Invalid trace token type!", idx); } trace(const trace& rhs) : tuple(rhs) {} @@ -124,16 +119,16 @@ class trace : protected tuple { } bool operator< (const trace& rhs) const { - return static_cast&>(this) < static_cast&>(rhs); + return *static_cast*>(this) < static_cast&>(rhs); } size_t encode_size() const { return tuple::encode_size(); } - void encode(char* buf, int& idx, size_t size) const { + void encode(char* buf, uintptr_t& idx, size_t size) const { tuple::encode(buf, idx, size); } - std::ostream& dump(std::ostream& out, const varbind* binding=NULL) const { + std::ostream& dump(std::ostream& out, const varbind* =NULL) const { return out << *static_cast*>(this); } @@ -141,7 +136,7 @@ class trace : protected tuple { static trace* tracer(trace_op op, const trace* token = NULL) { static trace save_token; static bool tracing = false; - static uint32_t clock = 0; + static long clock = 0; switch (op) { case TRACE_OFF: tracing = false; break; @@ -164,11 +159,11 @@ class trace : protected tuple { }; } //namespace marshal -} //namespace EIXX_NAMESPACE +} //namespace eixx namespace std { template - ostream& operator<< (ostream& out, const EIXX_NAMESPACE::marshal::trace& a) { + ostream& operator<< (ostream& out, const eixx::marshal::trace& a) { return a.dump(out); } diff --git a/include/eixx/marshal/tuple.hpp b/include/eixx/marshal/tuple.hpp index 9f63d37..094659c 100644 --- a/include/eixx/marshal/tuple.hpp +++ b/include/eixx/marshal/tuple.hpp @@ -10,23 +10,19 @@ /* ***** BEGIN LICENSE BLOCK ***** -This file is part of the eixx (Erlang C++ Interface) Library. +Copyright 2010 Serge Aleynikov -Copyright (C) 2010 Serge Aleynikov +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. + http://www.apache.org/licenses/LICENSE-2.0 -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. ***** END LICENSE BLOCK ***** */ @@ -34,6 +30,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #define _IMPL_TUPLE_HPP_ #include +#include #include #include #include @@ -41,7 +38,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include #include -namespace EIXX_NAMESPACE { +namespace eixx { namespace marshal { template class eterm; @@ -58,7 +55,17 @@ class tuple { } size_t get_init_size() const { BOOST_ASSERT(m_blob); - return m_blob->data()[m_blob->size()-1].to_long()-1; + return static_cast(m_blob->data()[m_blob->size()-1].to_long()-1); + } + + void release() { release(m_blob); m_blob = nullptr; } + + void release(blob, Alloc>* p) { + if (p && p->release(false)) { + for(size_t i=0, n=size(); i < n; i++) + p->data()[i].~eterm(); + p->free(); + } } protected: @@ -79,7 +86,14 @@ class tuple { explicit tuple(size_t arity, const Alloc& alloc = Alloc()) : m_blob(new blob, Alloc>(arity+1, alloc)) { + #ifndef __clang__ + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wclass-memaccess" + #endif memset(m_blob->data(), 0, sizeof(eterm)*m_blob->size()); + #ifndef __clang__ + #pragma GCC diagnostic pop + #endif set_init_size(0); } @@ -88,12 +102,15 @@ class tuple { m_blob->inc_rc(); } - template - tuple(const eterm (&items)[N], const Alloc& alloc = Alloc()) { - new (this) tuple(items, N, alloc); + tuple(tuple&& a) : m_blob(a.m_blob) { + a.m_blob = nullptr; } - tuple(const eterm items[], size_t a_size, const Alloc& alloc = Alloc()) + template + tuple(const eterm (&items)[N], const Alloc& alloc = Alloc()) + : tuple(items, N, alloc) {} + + tuple(const eterm* items, size_t a_size, const Alloc& alloc = Alloc()) : m_blob(new blob, Alloc>(a_size+1, alloc)) { for(size_t i=0; i < a_size; i++) { new (&m_blob->data()[i]) eterm(items[i]); @@ -101,26 +118,34 @@ class tuple { set_init_size(a_size); } + tuple(std::initializer_list> list, const Alloc& alloc = Alloc()) + : tuple(list.begin(), list.size(), alloc) {} + /** * Decode the tuple from a binary buffer. */ - tuple(const char* buf, int& idx, size_t size, const Alloc& a_alloc = Alloc()) - throw(err_decode_exception); + tuple(const char* buf, uintptr_t& idx, size_t size, const Alloc& a_alloc = Alloc()); ~tuple() { - if (m_blob && m_blob->release(false)) { - for(size_t i=0, n=size(); i < n; i++) - m_blob->data()[i].~eterm(); - m_blob->free(); - } + release(); } tuple& operator= (const tuple& rhs) { if (this != &rhs) { - blob, Alloc>* p = m_blob; + auto p = m_blob; + m_blob = rhs.m_blob; + if (m_blob) m_blob->inc_rc(); + release(p); + } + return *this; + } + + tuple& operator= (tuple&& rhs) { + if (this != &rhs) { + auto p = m_blob; m_blob = rhs.m_blob; - m_blob->inc_rc(); - if (p) p->release(); + rhs.m_blob = nullptr; + release(p); } return *this; } @@ -137,18 +162,32 @@ class tuple { return true; } - const eterm& operator[] (int idx) const { - BOOST_ASSERT(m_blob && (size_t)idx < size()); + bool operator< (const tuple& rhs) const { + if (size() < rhs.size()) + return true; + if (size() > rhs.size()) + return false; + for(const_iterator it1 = begin(), + it2 = rhs.begin(), iend = end(); it1 != iend; ++it1, ++it2) + { + if (!(*it1 < *it2)) + return false; + } + return true; + } + + const eterm& operator[] (size_t idx) const { + BOOST_ASSERT(m_blob && idx < size()); return m_blob->data()[idx]; } - eterm& operator[] (int idx) { - BOOST_ASSERT(m_blob && (size_t)idx < size()); + eterm& operator[] (size_t idx) { + BOOST_ASSERT(m_blob && idx < size()); return m_blob->data()[idx]; } template - void push_back(const T& t) throw (err_invalid_term) { + void push_back(const T& t) { BOOST_ASSERT(m_blob); if (initialized()) throw err_invalid_term("Attempt to change immutable tuple!"); @@ -174,14 +213,12 @@ class tuple { return result; } - void encode(char* buf, int& idx, size_t size) const; + void encode(char* buf, uintptr_t& idx, size_t size) const; + + bool subst(eterm& out, const varbind* binding) const; - bool subst(eterm& out, const varbind* binding) const - throw (err_unbound_variable); + bool match(const eterm& pattern, varbind* binding) const; - bool match(const eterm& pattern, varbind* binding) const - throw (err_invalid_term, err_unbound_variable); - std::ostream& dump(std::ostream& out, const varbind* vars = NULL) const; template @@ -311,17 +348,17 @@ class tuple { }; } // namespace marshal -} // namespace EIXX_NAMESPACE +} // namespace eixx namespace std { template - ostream& operator<< (ostream& out, const EIXX_NAMESPACE::marshal::tuple& a) { + ostream& operator<< (ostream& out, const eixx::marshal::tuple& a) { return a.dump(out); } } // namespace std -#include +#include #endif // _IMPL_TUPLE_HPP_ diff --git a/include/eixx/marshal/tuple.ipp b/include/eixx/marshal/tuple.hxx similarity index 69% rename from include/eixx/marshal/tuple.ipp rename to include/eixx/marshal/tuple.hxx index afdb818..a65b140 100644 --- a/include/eixx/marshal/tuple.ipp +++ b/include/eixx/marshal/tuple.hxx @@ -1,5 +1,5 @@ //---------------------------------------------------------------------------- -/// \file tuple.ipp +/// \file tuple.hxx //---------------------------------------------------------------------------- /// \brief Implementation of tuple's member functions. //---------------------------------------------------------------------------- @@ -9,23 +9,19 @@ /* ***** BEGIN LICENSE BLOCK ***** -This file is part of the eixx (Erlang C++ Interface) Library. +Copyright 2010 Serge Aleynikov -Copyright (C) 2010 Serge Aleynikov +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. + http://www.apache.org/licenses/LICENSE-2.0 -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. ***** END LICENSE BLOCK ***** */ @@ -37,18 +33,20 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include #include -namespace EIXX_NAMESPACE { +namespace eixx { namespace marshal { template -tuple::tuple(const char* buf, int& idx, size_t size, const Alloc& a_alloc) - throw(err_decode_exception) +tuple::tuple(const char* buf, uintptr_t& idx, size_t size, const Alloc& a_alloc) { - int arity; - if (ei_decode_tuple_header(buf, &idx, &arity) < 0) + BOOST_ASSERT(idx <= INT_MAX); + int n; + if (ei_decode_tuple_header(buf, (int*)&idx, &n) < 0) err_decode_exception("Error decoding tuple header", idx); + + size_t arity = static_cast(n); m_blob = new blob, Alloc>(arity+1, a_alloc); - for (int i=0; i < arity; i++) { + for (size_t i=0; i < arity; i++) { new (&m_blob->data()[i]) eterm(buf, idx, size, a_alloc); } set_init_size(arity); @@ -56,10 +54,14 @@ tuple::tuple(const char* buf, int& idx, size_t size, const Alloc& a_alloc } template -void tuple::encode(char* buf, int& idx, size_t size) const +void tuple::encode(char* buf, uintptr_t& idx, size_t size) const { BOOST_ASSERT(initialized()); - ei_encode_tuple_header(buf, &idx, this->size()); + BOOST_ASSERT(idx <= INT_MAX); + size_t arity = this->size(); + if (arity > INT_MAX) + throw err_encode_exception("LARGE_TUPLE_EXT arity exceeds maximum supported"); + ei_encode_tuple_header(buf, (int*)&idx, (int)arity); for(const_iterator it = begin(), iend=end(); it != iend; ++it) { visit_eterm_encoder visitor(buf, idx, size); visitor.apply_visitor(*it); @@ -69,7 +71,6 @@ void tuple::encode(char* buf, int& idx, size_t size) const template bool tuple::subst(eterm& out, const varbind* binding) const - throw (err_unbound_variable) { // We check if any contained term changes. bool changed = false; @@ -96,7 +97,6 @@ bool tuple::subst(eterm& out, const varbind* binding) const template bool tuple::match(const eterm& pattern, varbind* binding) const - throw (err_invalid_term, err_unbound_variable) { switch (pattern.type()) { case VAR: return pattern.match(eterm(*this), binding); @@ -128,4 +128,4 @@ std::ostream& tuple::dump(std::ostream& out, const varbind* vars) } } // namespace marshal -} // namespace EIXX_NAMESPACE +} // namespace eixx diff --git a/include/eixx/marshal/var.hpp b/include/eixx/marshal/var.hpp index 8ec7e60..bc02446 100644 --- a/include/eixx/marshal/var.hpp +++ b/include/eixx/marshal/var.hpp @@ -10,23 +10,19 @@ /* ***** BEGIN LICENSE BLOCK ***** -This file is part of the eixx (Erlang C++ Interface) Library. +Copyright 2010 Serge Aleynikov -Copyright (C) 2010 Serge Aleynikov +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. + http://www.apache.org/licenses/LICENSE-2.0 -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. ***** END LICENSE BLOCK ***** */ @@ -35,9 +31,10 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include #include +#include #include -namespace EIXX_NAMESPACE { +namespace eixx { namespace marshal { /** @@ -48,76 +45,110 @@ namespace marshal { * If you use '_' as variable, it will allways succeeds in matching, but * it will not be bound. **/ -template -class var : protected string +class var { - typedef string base_t; + atom m_name; + eterm_type m_type; + + template + bool check_type(const eterm& t) const { + return is_any() || m_type == UNDEFINED || t.type() == m_type + || (m_type == STRING && t.is_list() && t.to_list().empty()); + } + + eterm_type set(eterm_type t) { return m_name == am_ANY_ ? UNDEFINED : t; } + public: - var(const Alloc& a = Alloc()) : base_t("_", a) {} - var(const char* s, const Alloc& a = Alloc()) : base_t(s, a) {} - var(const std::string& s, const Alloc& a = Alloc()) : base_t(s.c_str(), s.size(), a) {} - var(const char* s, size_t n, const Alloc& a = Alloc()) : base_t(s, n, a) {} - var(const var& s) : base_t(s) {} + var(eterm_type t = UNDEFINED) + : var(am_ANY_, t) + { + static_assert(sizeof(var) == sizeof(uint64_t), "Invalid class size!"); + } - const char* c_str() const { return base_t::c_str(); } - const string& name() const { return *this; } - size_t size() const { return base_t::size(); } - size_t length() const { return base_t::length(); } + var(const atom& s, eterm_type t = UNDEFINED) : m_name(s) { m_type = set(t); } - bool is_any() const { return base_t::size() == 1 && c_str()[0] == '_'; } + var(const char* s, eterm_type t = UNDEFINED) : var(atom(s), t) {} + var(const std::string& s, eterm_type t = UNDEFINED) : var(atom(s), t) {} + template + var(const string& s, eterm_type t = UNDEFINED) : var(atom(s), t) {} + var(const char* s, size_t n, eterm_type t = UNDEFINED) : var(atom(s, n), t) {} + var(const var& v) : var(v.name(), v.type()) {} + + const char* c_str() const { return m_name.c_str(); } + const std::string& str() const { return m_name.to_string(); } + atom name() const { return m_name; } + size_t length() const { return m_name.length(); } + + eterm_type type() const { return m_type; } + bool is_any() const { return name() == am_ANY_; } + + std::string to_string() const { + std::stringstream s; + s << name().to_string() << type_to_type_string(type(), true); + return s.str(); + } template - bool operator==(const T&) const { return false; } + bool operator==(const T&) const { return false; } + + template + bool operator<(const T&) const { return false; } size_t encode_size() const { throw err_encode_exception("Cannot encode vars!"); } - void encode(char* buf, int& idx, size_t size) const { + void encode(char*, uintptr_t&, size_t) const { throw err_encode_exception("Cannot encode vars!"); - } + } + template const eterm* find_unbound(const varbind* binding = NULL) const { - if (is_any()) return NULL; - return binding ? binding->find(c_str()) : NULL; + return binding ? binding->find(name()) : NULL; } + template bool subst(eterm& out, const varbind* binding) const - throw (err_unbound_variable) { - if (is_any()) throw err_unbound_variable(c_str()); - const eterm* term = binding ? binding->find(c_str()) : NULL; - if (!term) throw err_unbound_variable(c_str()); + { + const eterm* term = binding ? binding->find(name()) : NULL; + if (!term || !check_type(*term)) + throw err_unbound_variable(c_str()); out = *term; return true; } + template bool match(const eterm& pattern, varbind* binding) const - throw (err_unbound_variable) { + { if (is_any()) return true; - const eterm* value = binding ? binding->find(c_str()) : NULL; + if (!binding) return false; + const eterm* value = binding->find(name()); if (value) - return value->match(pattern, binding); - if (binding != NULL) { - // Bind the variable - eterm et; - binding->bind(name(), pattern.subst(et, binding) ? et : pattern); - } + return check_type(*value) ? value->match(pattern, binding) : false; + if (!check_type(pattern)) + return false; + // Bind the variable + eterm et; + binding->bind(name(), pattern.subst(et, binding) ? et : pattern); return true; } + template std::ostream& dump(std::ostream& out, const varbind* binding = NULL) const { - if (is_any()) { return out << c_str(); } const eterm* term = binding ? binding->find(name()) : NULL; - return out << (term ? term->to_string(std::string::npos, binding) : *this); + return out << (term && check_type(*term) + ? term->to_string(std::string::npos, binding) : to_string()); } }; +BOOST_STATIC_ASSERT(sizeof(var) == 8); + } // namespace marshal -} // namespace EIXX_NAMESPACE +} // namespace eixx namespace std { template - ostream& operator<< (ostream& out, const EIXX_NAMESPACE::marshal::var& s) { - return s.dump(out); + ostream& operator<< (ostream& out, eixx::marshal::var s) { + return s.dump(out, (const eixx::marshal::varbind*)NULL); } } // namespace std diff --git a/include/eixx/marshal/varbind.hpp b/include/eixx/marshal/varbind.hpp index 8c789b2..1f52f12 100644 --- a/include/eixx/marshal/varbind.hpp +++ b/include/eixx/marshal/varbind.hpp @@ -9,23 +9,19 @@ /* ***** BEGIN LICENSE BLOCK ***** -This file is part of the eixx (Erlang C++ Interface) Library. +Copyright 2010 Serge Aleynikov -Copyright (C) 2010 Serge Aleynikov +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. + http://www.apache.org/licenses/LICENSE-2.0 -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. ***** END LICENSE BLOCK ***** */ @@ -38,9 +34,32 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include #include -namespace EIXX_NAMESPACE { +namespace eixx { namespace marshal { +/// Name-value pair associating an eterm with an atom name +template +struct epair : std::pair > { + typedef std::pair > base; + + epair(atom a_name, const eterm& a_value) : base(a_name, a_value) {} + +#if __cplusplus >= 201103L + epair(atom a_name, eterm&& a_value) : base(a_name, std::move(a_value)) {} + epair(const epair& a_rhs) : base(a_rhs) {} + epair(epair&& a_rhs) : base(std::forward(a_rhs)) {} + + epair& operator=(const epair& a_rhs) { + return base::operator=(static_cast(a_rhs)); + } + +#endif + + atom name() const { return base::first; } + const eterm& value() const { return base::second; } + eterm& value() { return base::second; } +}; + /** * This class maintains bindings of variables to values. */ @@ -52,18 +71,30 @@ class varbind { std::ostream& out, const varbind& binding); protected: - typedef std::map< - string, eterm, std::less< string >, Alloc - > eterm_map_t; + using eterm_map_t = + std::map< + atom, eterm, std::less, + typename std::allocator_traits:: + template rebind_alloc>> + >; public: explicit varbind(const Alloc& a_alloc = Alloc()) - : m_term_map(std::less< string >(), a_alloc) + : m_term_map(std::less(), a_alloc) {} varbind(const varbind& rhs) : m_term_map(rhs.m_term_map) {} +#if __cplusplus >= 201103L + varbind(std::initializer_list> a_list) { + m_term_map.insert(a_list.begin(), a_list.end()); + } +// varbind(std::initializer_list> a_list) { +// m_term_map.insert(a_list.begin(), a_list.end()); +// } +#endif + void copy(const varbind& rhs) { m_term_map = rhs.m_term_map; } /** @@ -73,12 +104,16 @@ class varbind { * @param a_term eterm to bind (must be non-zero). */ void bind(const char* a_var_name, const eterm& a_term) { - bind(string(a_var_name), a_term); + bind(atom(a_var_name), a_term); } void bind(const string& a_var_name, const eterm& a_term) { + bind(atom(a_var_name), a_term); + } + + void bind(atom a_var_name, const eterm& a_term) { // bind only if is unbound - m_term_map.insert(std::pair, eterm >(a_var_name, a_term)); + m_term_map.insert(std::make_pair(a_var_name, a_term)); } /** @@ -87,26 +122,46 @@ class varbind { * @return bound eterm pointer if variable is bound, 0 otherwise */ const eterm* - find(const char* a_var_name) const { return find(string(a_var_name)); } + find(const char* a_var_name) const { return find(atom(a_var_name)); } const eterm* - find(const string& a_var_name) const { + find(const string& a_var_name) const { return find(atom(a_var_name)); } + + const eterm* + find(atom a_var_name) const { typename eterm_map_t::const_iterator p = m_term_map.find(a_var_name); return (p != m_term_map.end()) ? &p->second : NULL; } const eterm* - operator[] (const char* a_var_name) const throw(err_unbound_variable) { - return (*this)[string(a_var_name)]; + operator[] (const char* a_var_name) const { + return (*this)[atom(a_var_name)]; + } + + const eterm* + operator[] (const string& a_var_name) const { + return (*this)[atom(a_var_name)]; } const eterm* - operator[] (const string& a_var_name) const throw(err_unbound_variable) { + operator[] (atom a_var_name) const { const eterm* p = find(a_var_name); if (!p) throw err_unbound_variable(a_var_name.c_str()); return p; } + const eterm& get(const char* a_var_name) const { + auto res = (*this)[a_var_name]; + assert (res); + return *res; + } + + const eterm& get(atom a_var_name) const { + auto res = (*this)[a_var_name]; + assert (res); + return *res; + } + /** * Merge all data in a variable binding with data in this * variable binding. @@ -118,19 +173,16 @@ class varbind { bind(it->first, it->second); } - /** - * Reset this binding - */ + /// Reset this binding void clear() { m_term_map.clear(); } - /** - * Convert varbind to string - */ + /// Convert varbind to string void dump(std::ostream& out) const { out << *this; } - /** - * Return the number of bound variables held in internal dictionary. - */ + /// Dump to string + std::string to_string() const { std::stringstream s; dump(s); return s.str(); } + + /// Return the number of bound variables held in internal dictionary. size_t count() const { return m_term_map.size(); } protected: @@ -138,17 +190,17 @@ class varbind { }; } // namespace marshal -} // namespace EIXX_NAMESPACE +} // namespace eixx namespace std { template - ostream& operator<< (ostream& out, const EIXX_NAMESPACE::marshal::varbind& binding) { - using namespace EIXX_NAMESPACE::marshal; + ostream& operator<< (ostream& out, const eixx::marshal::varbind& binding) { + using namespace eixx::marshal; for (typename varbind::eterm_map_t::const_iterator it = binding.m_term_map.begin(); it != binding.m_term_map.end(); ++it) - out << " " << it->first << " = " << it->second << std::endl; + out << " " << it->first.to_string() << " = " << it->second << std::endl; return out; } diff --git a/include/eixx/marshal/visit.hpp b/include/eixx/marshal/visit.hpp index 8f3e400..2febad3 100644 --- a/include/eixx/marshal/visit.hpp +++ b/include/eixx/marshal/visit.hpp @@ -10,23 +10,19 @@ /* ***** BEGIN LICENSE BLOCK ***** -This file is part of the eixx (Erlang C++ Interface) Library. +Copyright 2010 Serge Aleynikov -Copyright (C) 2010 Serge Aleynikov +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. + http://www.apache.org/licenses/LICENSE-2.0 -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. ***** END LICENSE BLOCK ***** */ @@ -35,12 +31,12 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include -namespace EIXX_NAMESPACE { +namespace eixx { namespace marshal { template class tuple; template class list; - template class var; + class var; template struct wrap { @@ -78,6 +74,6 @@ struct static_visitor { }; } // namespace marshal -} // namespace EIXX_NAMESPACE +} // namespace eixx #endif // _IMPL_VISIT_HPP_ diff --git a/include/eixx/marshal/visit_encode_size.hpp b/include/eixx/marshal/visit_encode_size.hpp index 0ec8461..08a4fa8 100644 --- a/include/eixx/marshal/visit_encode_size.hpp +++ b/include/eixx/marshal/visit_encode_size.hpp @@ -9,23 +9,19 @@ /* ***** BEGIN LICENSE BLOCK ***** -This file is part of the eixx (Erlang C++ Interface) Library. +Copyright 2010 Serge Aleynikov -Copyright (C) 2010 Serge Aleynikov +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. + http://www.apache.org/licenses/LICENSE-2.0 -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. ***** END LICENSE BLOCK ***** */ @@ -35,22 +31,22 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include #include -namespace EIXX_NAMESPACE { +namespace eixx { namespace marshal { template struct visit_eterm_encode_size_calc : public static_visitor, size_t> { - size_t operator()(bool a) const { return 3 + (a ? 4 : 5); } - size_t operator()(double a) const { return 9; } - size_t operator()(long a) const { int n = 0; ei_encode_longlong(NULL, &n, a); return n; } + size_t operator()(bool a) const { return 2 + (a ? 4 : 5); } + size_t operator()(double ) const { return 9; } + size_t operator()(long a) const { int n = 0; ei_encode_longlong(NULL, &n, a); return static_cast(n); } template size_t operator()(const T& a) const { return a.encode_size(); } }; -} // namespace EIXX_NAMESPACE -} // namespace EIXX_NAMESPACE +} // namespace eixx +} // namespace eixx #endif // _IMPL_VISIT_ENCODE_SIZE_HPP_ diff --git a/include/eixx/marshal/visit_encoder.hpp b/include/eixx/marshal/visit_encoder.hpp index e0eba3f..f2bc1fa 100644 --- a/include/eixx/marshal/visit_encoder.hpp +++ b/include/eixx/marshal/visit_encoder.hpp @@ -9,23 +9,19 @@ /* ***** BEGIN LICENSE BLOCK ***** -This file is part of the eixx (Erlang C++ Interface) Library. +Copyright 2010 Serge Aleynikov -Copyright (C) 2010 Serge Aleynikov +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. + http://www.apache.org/licenses/LICENSE-2.0 -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. ***** END LICENSE BLOCK ***** */ @@ -34,27 +30,36 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include -namespace EIXX_NAMESPACE { +namespace eixx { namespace marshal { class visit_eterm_encoder: public static_visitor { mutable char* buf; - int& idx; + uintptr_t& idx; const size_t size; public: - visit_eterm_encoder(char* a_buf, int& a_idx, size_t a_size) + visit_eterm_encoder(char* a_buf, uintptr_t& a_idx, size_t a_size) : buf(a_buf), idx(a_idx), size(a_size) {} - void operator() (bool a) const { ei_encode_boolean (buf, &idx, a); } - void operator() (long a) const { ei_encode_longlong(buf, &idx, a); } - void operator() (double a) const { ei_encode_double (buf, &idx, a); } + void operator() (bool a) const { + BOOST_ASSERT(idx <= INT_MAX); + ei_encode_boolean(buf, (int*)&idx, a); + } + void operator() (long a) const { + BOOST_ASSERT(idx <= INT_MAX); + ei_encode_longlong(buf, (int*)&idx, a); + } + void operator() (double a) const { + BOOST_ASSERT(idx <= INT_MAX); + ei_encode_double(buf, (int*)&idx, a); + } template void operator()(const T& a) const { a.encode(buf, idx, size); } }; } // namespace marshal -} // namespace EIXX_NAMESPACE +} // namespace eixx #endif // _IMPL_VISIT_ENCODER_HPP_ diff --git a/include/eixx/marshal/visit_match.hpp b/include/eixx/marshal/visit_match.hpp index 92e1e63..9bb3e66 100644 --- a/include/eixx/marshal/visit_match.hpp +++ b/include/eixx/marshal/visit_match.hpp @@ -9,23 +9,19 @@ /* ***** BEGIN LICENSE BLOCK ***** -This file is part of the eixx (Erlang C++ Interface) Library. +Copyright 2010 Serge Aleynikov -Copyright (C) 2010 Serge Aleynikov +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. + http://www.apache.org/licenses/LICENSE-2.0 -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. ***** END LICENSE BLOCK ***** */ @@ -38,7 +34,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include #include -namespace EIXX_NAMESPACE { +namespace eixx { namespace marshal { template @@ -53,7 +49,7 @@ class visit_eterm_match bool operator()(const tuple& a) const { return a.match(m_pattern, m_binding); } bool operator()(const list& a) const { return a.match(m_pattern, m_binding); } - bool operator()(const var& a) const { return a.match(m_pattern, m_binding); } + bool operator()(const var& a) const { return a.match(m_pattern, m_binding); } template bool operator()(const T& a) const { @@ -66,7 +62,7 @@ class visit_eterm_match } }; -} // namespace EIXX_NAMESPACE -} // namespace EIXX_NAMESPACE +} // namespace eixx +} // namespace eixx #endif // _IMPL_VISIT_MATCH_HPP_ diff --git a/include/eixx/marshal/visit_subst.hpp b/include/eixx/marshal/visit_subst.hpp index 5d6eacd..025549c 100644 --- a/include/eixx/marshal/visit_subst.hpp +++ b/include/eixx/marshal/visit_subst.hpp @@ -9,23 +9,19 @@ /* ***** BEGIN LICENSE BLOCK ***** -This file is part of the eixx (Erlang C++ Interface) Library. +Copyright 2010 Serge Aleynikov -Copyright (C) 2010 Serge Aleynikov +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. + http://www.apache.org/licenses/LICENSE-2.0 -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. ***** END LICENSE BLOCK ***** */ @@ -35,10 +31,10 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include //#include //#include -//#include +#include #include -namespace EIXX_NAMESPACE { +namespace eixx { namespace marshal { template @@ -53,13 +49,13 @@ class visit_eterm_subst bool operator()(const tuple& a) const { return a.subst(m_out, m_binding); } bool operator()(const list& a) const { return a.subst(m_out, m_binding); } - bool operator()(const var& a) const { return a.subst(m_out, m_binding); } + bool operator()(const var& a) const { return a.subst(m_out, m_binding); } template - bool operator()(const T& a) const { return false; } + bool operator()(const T&) const { return false; } }; -} // namespace EIXX_NAMESPACE -} // namespace EIXX_NAMESPACE +} // namespace eixx +} // namespace eixx #endif // _IMPL_VISIT_SUBST_HPP_ diff --git a/include/eixx/marshal/visit_to_string.hpp b/include/eixx/marshal/visit_to_string.hpp index fa4bee3..36995d6 100644 --- a/include/eixx/marshal/visit_to_string.hpp +++ b/include/eixx/marshal/visit_to_string.hpp @@ -9,23 +9,19 @@ /* ***** BEGIN LICENSE BLOCK ***** -This file is part of the eixx (Erlang C++ Interface) Library. +Copyright 2010 Serge Aleynikov -Copyright (C) 2010 Serge Aleynikov +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. + http://www.apache.org/licenses/LICENSE-2.0 -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. ***** END LICENSE BLOCK ***** */ @@ -37,7 +33,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include #include -namespace EIXX_NAMESPACE { +namespace eixx { namespace marshal { template @@ -65,6 +61,6 @@ class visit_eterm_stringify }; } // namespace marshal -} // namespace EIXX_NAMESPACE +} // namespace eixx #endif // _IMPL_VISIT_TO_STRING_HPP_ diff --git a/include/eixx/util/async_queue.hpp b/include/eixx/util/async_queue.hpp new file mode 100644 index 0000000..e45ff4c --- /dev/null +++ b/include/eixx/util/async_queue.hpp @@ -0,0 +1,226 @@ +//---------------------------------------------------------------------------- +/// \file async_queue.hpp +/// \author Serge Aleynikov +//---------------------------------------------------------------------------- +/// \brief Multi-producer / single-consumer waitable queue. +//---------------------------------------------------------------------------- +// Created: 2013-10-10 +//---------------------------------------------------------------------------- +/* +***** BEGIN LICENSE BLOCK ***** + +Copyright 2010 Serge Aleynikov + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +***** END LICENSE BLOCK ***** +*/ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace eixx { +namespace util { + +using namespace boost::system::errc; + +/** + * Implements an asyncronous multiple-writer-single-reader queue + * for use with BOOST ASIO. + */ +template> +struct async_queue : boost::enable_shared_from_this> +{ + using queue_type = boost::lockfree::queue + , boost::lockfree::capacity<1023>, + boost::lockfree::fixed_sized>; + + using async_handler = + std::function; +private: + boost::asio::io_service& m_io; + queue_type m_queue; + int m_batch_size; + boost::asio::system_timer m_timer; + + int dec_repeat_count(int n) { + return n == std::numeric_limits::max() || !n ? n : n-1; + } + + // Dequeue up to m_batch_size of items and for each one call + // m_wait_handler + template + void process_queue(const Handler& h, [[maybe_unused]] const boost::system::error_code& ec, + std::chrono::milliseconds repeat, int repeat_count) { + // Process up to m_batch_size items waiting on the queue. + // For each dequeued item call m_wait_handler + + int i = 0; // Number of handler invocations + + T value; + while (i < m_batch_size && m_queue.pop(value)) { + i++; + repeat_count = dec_repeat_count(repeat_count); + if (!h(value, boost::system::error_code())) + return; + } + + static const auto s_timeout = + boost::system::errc::make_error_code(boost::system::errc::timed_out); + + auto pthis = this->shared_from_this(); + + // If we reached the batch size and queue has more data + // to process - give up the time slice and reschedule the handler + if (i == m_batch_size && !m_queue.empty()) { + m_io.post([pthis, h, repeat, repeat_count]() { + (*pthis)(h, boost::asio::error::operation_aborted, repeat, repeat_count); + }); + return; + } else if (!i && !h(value, s_timeout)) { + // If we haven't processed any data and the timer was canceled. + // Invoke the callback to see if we need to remove the handler. + return; + } + + int n = dec_repeat_count(repeat_count); + + // If requested repeated timer, schedule new timer invocation + if (repeat > std::chrono::milliseconds(0) && n > 0) { + m_timer.cancel(); + m_timer.expires_from_now(repeat); + m_timer.async_wait( + [pthis, h, repeat, n] + (const boost::system::error_code& a_ec) { + (*pthis)(h, a_ec, repeat, n); + }); + } + } + + // Called by io_service on timeout of m_timer + template + void operator() (const Handler& h, const boost::system::error_code& ec, + std::chrono::milliseconds repeat, int repeat_count) { + process_queue(h, ec, repeat, repeat_count); + } + +public: + async_queue(boost::asio::io_service& a_io, + int a_batch_size = 16, const Alloc& a_alloc = Alloc()) + : m_io(a_io) + , m_queue(a_alloc) + , m_batch_size(a_batch_size) + , m_timer(a_io) + {} + + ~async_queue() { + reset(); + } + + void reset() { + cancel(); + + T value; + while (m_queue.pop(value)); + } + + int batch_size() const { return m_batch_size; } + void batch_size(int sz) { m_batch_size = sz; } + + bool cancel() { + boost::system::error_code ec; + return m_timer.cancel(ec); + } + + bool enqueue(T const& data, bool notify = true) { + if (!m_queue.push(data)) + return false; + + if (!notify) return true; + + boost::system::error_code ec; + m_timer.cancel(ec); + + return true; + } + + bool dequeue(T& value) { + return m_queue.pop(value); + } + + /// Call \a a_on_data handler asyncronously on next message in the queue. + /// + /// @returns true if the call was handled synchronously + template + bool async_dequeue(const Handler& a_on_data, int repeat_count = 0) { + return async_dequeue(a_on_data, std::chrono::milliseconds(-1), repeat_count); + } + + /// Call \a a_on_data handler asyncronously on next message in the queue. + /// + /// @returns true if the call was handled synchronously + template + bool async_dequeue(const Handler& a_on_data, + std::chrono::milliseconds a_wait_duration = std::chrono::milliseconds(-1), + int repeat_count = 0) + { + T value; + if (m_queue.pop(value)) { + if (!a_on_data(value, boost::system::error_code())) + return true; + if (repeat_count > 0) --repeat_count; + } + + if (!repeat_count) + return true; + + auto rep = repeat_count < 0 ? std::numeric_limits::max() : repeat_count; + + std::chrono::milliseconds timeout = + a_wait_duration < std::chrono::milliseconds(0) + ? std::chrono::milliseconds::max() + : a_wait_duration; + + if (timeout == std::chrono::milliseconds(0)) + m_io.post([this, &a_on_data, timeout, rep]() { + (*this->shared_from_this())( + a_on_data, boost::system::error_code(), timeout, rep); + }); + else { + boost::system::error_code ec; + m_timer.cancel(ec); + m_timer.expires_from_now(timeout); + auto pthis = this->shared_from_this(); + m_timer.async_wait( + [pthis, &a_on_data, timeout, rep] + (const boost::system::error_code& e) { + (*pthis)(a_on_data, e, timeout, rep); + } + ); + } + + return false; + } +}; + +} // namespace util +} // namespace eixx diff --git a/include/eixx/util/async_wait_timeout.hpp b/include/eixx/util/async_wait_timeout.hpp index baee539..0c4bd1d 100644 --- a/include/eixx/util/async_wait_timeout.hpp +++ b/include/eixx/util/async_wait_timeout.hpp @@ -1,55 +1,10 @@ #ifndef _ASYNC_WAIT_TIMEOUT_HPP_ #define _ASYNC_WAIT_TIMEOUT_HPP_ -#include +#include +#include namespace boost { - -namespace asio { -namespace error { - - enum timer_errors { - timeout = ETIMEDOUT - }; - - namespace detail { - - struct timer_category : public boost::system::error_category { - const char* name() const { return "asio.timer"; } - - std::string message(int value) const { - if (value == error::timeout) - return "Operation timed out"; - return "asio.timer error"; - } - }; - - } // namespace detail - - inline const boost::system::error_category& get_timer_category() { - static detail::timer_category instance; - return instance; - } - - static const boost::system::error_category& timer_category - = boost::asio::error::get_timer_category(); - -} // namespace error -} // namespace asio - -namespace system { - - template<> struct is_error_code_enum { - static const bool value = true; - }; - - inline boost::system::error_code make_error_code(boost::asio::error::timer_errors e) { - return boost::system::error_code( - static_cast(e), boost::asio::error::get_timer_category()); - } - -} // namespace system - namespace asio { class deadline_timer_ex : public basic_deadline_timer @@ -64,7 +19,7 @@ namespace asio { //if (ec == error::operation_aborted) // return; system::error_code e = ec == system::error_code() - ? system::make_error_code(error::timeout) + ? make_error_code(error::timeout) : ec; m_h(e); } @@ -99,8 +54,7 @@ namespace asio { } }; - } // namespace asio - +} // namespace asio } // namespace boost #endif // _ASYNC_WAIT_TIMEOUT_HPP_ diff --git a/include/eixx/util/atom_table.hpp b/include/eixx/util/atom_table.hpp new file mode 100644 index 0000000..c2bfd28 --- /dev/null +++ b/include/eixx/util/atom_table.hpp @@ -0,0 +1,180 @@ +//---------------------------------------------------------------------------- +/// \file atom.hpp +//---------------------------------------------------------------------------- +/// \brief A class implementing an atom - enumerated string stored +/// stored in non-garbage collected hash table of fixed size. +//---------------------------------------------------------------------------- +// Copyright (c) 2010 Serge Aleynikov +// Created: 2010-09-20 +//---------------------------------------------------------------------------- +/* +***** BEGIN LICENSE BLOCK ***** + +Copyright 2010 Serge Aleynikov + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +***** END LICENSE BLOCK ***** +*/ +#ifndef _EIXX_ATOM_TABLE_HPP_ +#define _EIXX_ATOM_TABLE_HPP_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace eixx { +namespace util { + + namespace eid = eixx::detail; + using eid::lock_guard; + + inline size_t utf8_length(const std::string& str) { + size_t count = 0; + const char* s = str.c_str(); + while (*s) count += (*s++ & 0xc0) != 0x80; + return count; + } + + /// Non-garbage collected hash table for atoms. It stores strings + /// as atoms so that atoms can be quickly compared to with O(1) + /// complexity. The instance of this table is statically maintained + /// and its content is never cleared. The table contains a unique + /// list of strings represented as atoms added throughout the lifetime + /// of the application. + template + < + typename String = std::string, + typename Vector = std::vector, + typename HashMap = char_int_hash_map, + typename Mutex = eid::mutex + > + class basic_atom_table : private detail::hsieh_hash_fun { + static constexpr size_t s_default_max_atoms = 1024*1024; + + size_t find_value(size_t bucket, const char* a_atom) { + typename HashMap::const_local_iterator + lit = m_index.begin(bucket), lend = m_index.end(bucket); + while(lit != lend && strcmp(a_atom, lit->first) != 0) ++lit; + return lit == lend ? 0 : lit->second; + } + public: + /// Returns the default atom table maximum size. The value can be + /// changed by setting the EI_ATOM_TABLE_SIZE environment variable. + static size_t default_size() { + // See: https://erlang.org/doc/efficiency_guide/advanced.html#atoms + const char* p = getenv("EI_ATOM_TABLE_SIZE"); + size_t n = 0; + if (p) { + std::stringstream ss(p); + ss >> n; + } + // Reserve capacity up to UINT32_MAX + if (n > UINT32_MAX) n = UINT32_MAX; + return n > 0 ? n : s_default_max_atoms; + } + + /// Returns the maximum number of atoms that can be stored in the atom table. + size_t capacity() const { return m_atoms.capacity(); } + + /// Returns the current number of atoms stored in the atom table. + size_t allocated() const { return m_atoms.size(); } + + explicit basic_atom_table(size_t a_max_atoms = default_size()) + : m_index(a_max_atoms) { + m_atoms.reserve(a_max_atoms); + m_atoms.push_back(""); // The 0-th element is an empty atom (""). + m_index[""] = 0; + } + + ~basic_atom_table() { + lock_guard guard(m_lock); + m_atoms.clear(); + m_index.clear(); + } + + /// Lookup an atom in the atom table by index. + const String& get(size_t n) const { return (*this)[n]; } + + /// Lookup an atom in the atom table by index. + const String& operator[] (size_t n) const { + BOOST_ASSERT((size_t)n < m_atoms.size()); + return m_atoms[n]; + } + + /// Try to lookup an atom in the atom table + /// @return -1 if the atom by name is not found, -2 if the atom is invalid, + /// or a value >= 0, if an existing atom is found. + std::pair try_lookup(const char* a_name, size_t n) { return try_lookup(String(a_name, n)); } + std::pair try_lookup(const char* a_name) { return try_lookup(String(a_name)); } + std::pair try_lookup(const String& a_name) + { + if (a_name.size() == 0) + return {true, 0}; + if (a_name.size() > MAXATOMLEN_UTF8 || utf8_length(a_name) > MAXATOMLEN) + return {false, 2}; + size_t bucket = m_index.bucket(a_name.c_str()); + size_t n = find_value(bucket, a_name.c_str()); + if (n > 0) + return {true, n}; + else + return {false, 1}; + } + + /// Lookup an atom in the atom table by name. If the atom is not + /// present in the atom table - add it. Return the index of the + /// atom in the atom table. + /// @throw std::runtime_error if atom table is full. + /// @throw err_bad_argument if atom size is longer than MAXATOMLEN + size_t lookup(const char* a_name, size_t n) { return lookup(String(a_name, n)); } + size_t lookup(const char* a_name) { return lookup(String(a_name)); } + size_t lookup(const String& a_name) + { + std::pair p = try_lookup(a_name); + size_t n = p.second; + if (p.first) return n; + if (!p.first && n == 2) throw err_bad_argument("Atom size is too long!"); + + lock_guard guard(m_lock); + if (!std::is_same::value) { + size_t bucket = m_index.bucket(a_name.c_str()); + n = find_value(bucket, a_name.c_str()); + if (n > 0) + return n; + } + + n = m_atoms.size(); + if (n == m_atoms.capacity()) + throw std::runtime_error("Atom hash table is full!"); + m_atoms.push_back(a_name); + m_index[m_atoms.back().c_str()] = n; + return n; + } + private: + Vector m_atoms; + HashMap m_index; + Mutex m_lock; + }; + + typedef basic_atom_table<> atom_table; + +} // namespace util +} // namespace eixx + +#endif diff --git a/include/eixx/util/common.hpp b/include/eixx/util/common.hpp index c9d8981..57c229f 100644 --- a/include/eixx/util/common.hpp +++ b/include/eixx/util/common.hpp @@ -1,7 +1,7 @@ //---------------------------------------------------------------------------- /// \file common.hpp //---------------------------------------------------------------------------- -/// \namespace dmf Distributed Monitoring Framework. +/// \namespace eixx EI C++ Interface Library /// /// This file contains a set of commonly used functions. //---------------------------------------------------------------------------- @@ -18,17 +18,20 @@ #include #include #include +#include +#include + #ifdef HAVE_CONFIG_H -# include +#include #endif #define ERL_MONITOR_P 19 #define ERL_DEMONITOR_P 20 #define ERL_MONITOR_P_EXIT 21 -namespace EIXX_NAMESPACE { +namespace eixx { -#if BOOST_VERSION > 104800 +#if BOOST_VERSION >= 104800 namespace bid = boost::interprocess::ipcdetail; #else namespace bid = boost::interprocess::detail; @@ -47,7 +50,7 @@ namespace bid = boost::interprocess::detail; /// \def ON_ERROR_CALLBACK(Client, S) /// Invokes an error callback from within perc_client's implementation. /// -/// @param Client is of type 'dmf::client' +/// @param Connection is of type 'eixx::connection' /// @param S is the stream of elements to include in the message. /// The elements can be concatinated with left shift /// notation. @@ -71,16 +74,10 @@ int __inline__ log2(unsigned long n, uint8_t base = 2) { return n == 1 ? 0 : 1+log2(n/base, base); } -static __inline__ unsigned long bit_scan_forward(unsigned long v) -{ - unsigned long r; - __asm__ __volatile__( - #if (__SIZEOF_LONG__ == 8) - "bsfq %1, %0": "=r"(r): "rm"(v) ); - #else - "bsfl %1, %0": "=r"(r): "rm"(v) ); - #endif - return r; +/// Note, that bit_scan_forward(0) leads to UB +static __inline__ int bit_scan_forward(unsigned long v) +{ + return __builtin_ctzl(v); } /// Wrapper for basic atomic operations over an integer. @@ -108,7 +105,7 @@ struct atomic { /// Return the index of a_string in the a_list using a_default index if /// a_string is not found in the list. -template +template int find_index(const char* (&a_list)[N], const char* a_string, int a_default=-1) { for (int i=0; i < N; i++) if (strcmp(a_string, a_list[i]) == 0) @@ -116,7 +113,7 @@ int find_index(const char* (&a_list)[N], const char* a_string, int a_default=-1) return a_default; } -} // namespace EIXX_NAMESPACE +} // namespace eixx #endif // _EIXX_COMMON_HPP_ diff --git a/include/eixx/util/compiler_hints.hpp b/include/eixx/util/compiler_hints.hpp new file mode 100644 index 0000000..a23e9d3 --- /dev/null +++ b/include/eixx/util/compiler_hints.hpp @@ -0,0 +1,44 @@ +//---------------------------------------------------------------------------- +/// \file compiler_hints.hpp +//---------------------------------------------------------------------------- +/// \namespace eixx +/// +/// This file contains various compiler optimization hints. +//---------------------------------------------------------------------------- +// Copyright (c) 2010 Serge Aleynikov +// Created: 2010-09-20 +//---------------------------------------------------------------------------- + +#pragma once + +// Branch prediction optimization (see http://lwn.net/Articles/255364/) + +#ifndef NO_HINT_BRANCH_PREDICTION +# ifndef LIKELY +# define LIKELY(expr) __builtin_expect(!!(expr),1) +# endif +# ifndef UNLIKELY +# define UNLIKELY(expr) __builtin_expect(!!(expr),0) +# endif +#else +# ifndef LIKELY +# define LIKELY(expr) (expr) +# endif +# ifndef UNLIKELY +# define UNLIKELY(expr) (expr) +# endif +#endif + +namespace eixx { + +// Though the compiler should optimize this inlined code in the same way as +// when using LIKELY/UNLIKELY macros directly the preference is to use the later +#ifndef NO_HINT_BRANCH_PREDICTION + inline bool likely(bool expr) { return __builtin_expect(!!(expr),1); } + inline bool unlikely(bool expr) { return __builtin_expect(!!(expr),0); } +#else + inline bool likely(bool expr) { return expr; } + inline bool unlikely(bool expr) { return expr; } +#endif + +} // namespace eixx diff --git a/include/eixx/util/hashtable.hpp b/include/eixx/util/hashtable.hpp index 8caf2de..b3ea568 100644 --- a/include/eixx/util/hashtable.hpp +++ b/include/eixx/util/hashtable.hpp @@ -1,23 +1,19 @@ /* ***** BEGIN LICENSE BLOCK ***** -This file is part of the EPI (Erlang Plus Interface) Library. +Copyright 2010 Serge Aleynikov -Copyright (C) 2010 Serge Aleynikov +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. + http://www.apache.org/licenses/LICENSE-2.0 -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. ***** END LICENSE BLOCK ***** */ @@ -26,17 +22,17 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include -#ifdef __GXX_EXPERIMENTAL_CXX0X__ +#if defined(__GXX_EXPERIMENTAL_CXX0X__) || __cplusplus >= 201103L #include #else #include #endif -namespace EIXX_NAMESPACE { +namespace eixx { namespace detail { namespace src = - #ifdef __GXX_EXPERIMENTAL_CXX0X__ + #if defined(__GXX_EXPERIMENTAL_CXX0X__) || __cplusplus >= 201103L std; #else boost; @@ -60,8 +56,9 @@ struct hsieh_hash_fun { static uint16_t get16bits(const char* d) { return *(const uint16_t *)d; } size_t operator()(const char* data) const { - int len = strlen(data); - uint32_t hash = len, tmp; + size_t len = strlen(data); + BOOST_ASSERT(len <= UINT32_MAX); + uint32_t hash = (uint32_t)len, tmp; int rem; if (len <= 0 || data == NULL) return 0; @@ -82,14 +79,14 @@ struct hsieh_hash_fun { switch (rem) { case 3: hash += get16bits (data); hash ^= hash << 16; - hash ^= data[sizeof (uint16_t)] << 18; + hash ^= uint32_t(data[sizeof (uint16_t)]) << 18; hash += hash >> 11; break; case 2: hash += get16bits (data); hash ^= hash << 11; hash += hash >> 17; break; - case 1: hash += *data; + case 1: hash += uint32_t(*data); hash ^= hash << 10; hash += hash >> 1; } @@ -102,7 +99,7 @@ struct hsieh_hash_fun { hash ^= hash << 25; hash += hash >> 6; - return hash; + return static_cast(hash); } }; @@ -110,7 +107,7 @@ struct hsieh_hash_fun { typedef detail::hash_map_base char_int_hash_map; -} // namespace EIXX_NAMESPACE +} // namespace eixx #endif // _EIXX_HASHTABLE_HPP_ diff --git a/include/eixx/util/string_util.hpp b/include/eixx/util/string_util.hpp index 965ef10..7a25ed1 100644 --- a/include/eixx/util/string_util.hpp +++ b/include/eixx/util/string_util.hpp @@ -10,23 +10,19 @@ /* ***** BEGIN LICENSE BLOCK ***** -This file is part of the eixx (Erlang C++ Interface) Library. +Copyright 2010 Serge Aleynikov -Copyright (C) 2010 Serge Aleynikov +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. + http://www.apache.org/licenses/LICENSE-2.0 -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. ***** END LICENSE BLOCK ***** */ @@ -38,12 +34,13 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include #include -namespace EIXX_NAMESPACE { +namespace eixx { /// Print the content of a buffer to \a out stream in the form: /// \verbatim <> \endverbatim where Ik is /// unsigned integer less than 256. -static inline std::ostream& to_binary_string(std::ostream& out, const char* buf, size_t sz) { +template +inline Stream& to_binary_string(Stream& out, const char* buf, size_t sz) { out << "<<"; const char* begin = buf, *end = buf + sz; for(const char* p = begin; p != end; ++p) { @@ -56,17 +53,49 @@ static inline std::ostream& to_binary_string(std::ostream& out, const char* buf, /// Convert the content of a buffer to a binary string in the form: /// \verbatim <> \endverbatim where Ik is /// unsigned integer less than 256. -static inline std::string to_binary_string(const char* a, size_t sz) { +inline std::string to_binary_string(const char* a, size_t sz) { std::stringstream oss; to_binary_string(oss, a, sz); return oss.str(); } -} // namespace EIXX_NAMESPACE +/// Convert string to integer +/// +/// @tparam TillEOL instructs that the integer must be validated till a_end. +/// If false, "123ABC" is considered a valid 123 number. Otherwise +/// the function will return NULL. +/// @return input string ptr beyond the the value read if successful, NULL otherwise +// +template +inline const char* fast_atoi(const char* a_str, const char* a_end, T& res) { + if (a_str >= a_end) return nullptr; + + bool l_neg; + + if (*a_str == '-') { l_neg = true; ++a_str; } + else { l_neg = false; } + + T x = 0; + + do { + const int c = *a_str - '0'; + if (c < 0 || c > 9) { + if (TillEOL) + return nullptr; + break; + } + x = (x << 3) + (x << 1) + c; + } while (++a_str != a_end); + + res = l_neg ? -x : x; + return a_str; +} + +} // namespace eixx namespace std { - template + template std::string to_string(const uint8_t (&s)[N]) { return std::string((const char*)s, N); } } diff --git a/include/eixx/util/sync.hpp b/include/eixx/util/sync.hpp index a5cc62c..d058fd4 100644 --- a/include/eixx/util/sync.hpp +++ b/include/eixx/util/sync.hpp @@ -1,23 +1,19 @@ /* ***** BEGIN LICENSE BLOCK ***** -This file is part of the EPI (Erlang Plus Interface) Library. +Copyright 2010 Serge Aleynikov -Copyright (C) 2010 Serge Aleynikov +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. + http://www.apache.org/licenses/LICENSE-2.0 -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. ***** END LICENSE BLOCK ***** */ @@ -26,34 +22,47 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include -#ifdef __GXX_EXPERIMENTAL_CXX0X__ +#if defined(__GXX_EXPERIMENTAL_CXX0X__) || __cplusplus >= 201103L #include #else #include #endif -namespace EIXX_NAMESPACE { +namespace eixx { namespace detail { -#ifdef __GXX_EXPERIMENTAL_CXX0X__ -typedef std::mutex mutex; -typedef std::recursive_mutex recursive_mutex; -template struct lock_guard: public std::lock_guard { - lock_guard(Mutex& a_m) : std::lock_guard(a_m) {} +struct null_mutex { + void lock() {} + void unlock() noexcept {} + bool try_lock() { return true; } }; -#else + +#if __cplusplus < 201103L typedef boost::mutex mutex; typedef boost::recursive_mutex recursive_mutex; // Note that definition of boost::lock_guard is missing mutex_type compared // to the definition of std::lock_guard. template struct lock_guard: public boost::lock_guard { - typedef boost::mutex mutex_type; + typedef Mutex mutex_type; lock_guard(Mutex& a_m) : boost::lock_guard(a_m) {} }; +#else +template struct lock_guard: public std::lock_guard { + lock_guard(Mutex& a_m) : std::lock_guard(a_m) {} +}; + #ifdef EIXX_NULL_MUTEX + typedef null_mutex mutex; + typedef null_mutex recursive_mutex; + template struct lock_guard: public std::lock_guard { + lock_guard(Mutex& a_m) : std::lock_guard(a_m) {} + }; + #else + typedef std::mutex mutex; + typedef std::recursive_mutex recursive_mutex; + #endif #endif } // namespace detail -} // namespace EIXX_NAMESPACE +} // namespace eixx #endif // _EIXX_SYNC_HPP_ - diff --git a/include/eixx/util/timeout.hpp b/include/eixx/util/timeout.hpp new file mode 100644 index 0000000..6e792d1 --- /dev/null +++ b/include/eixx/util/timeout.hpp @@ -0,0 +1,50 @@ +#ifndef _EIXX_TIMEOUT_HPP_ +#define _EIXX_TIMEOUT_HPP_ + +#include + +namespace boost { +namespace asio { +namespace error { + + enum timer_errors { + timeout = ETIMEDOUT + }; + + namespace detail { + + struct timer_category : public boost::system::error_category { + const char* name() const noexcept { return "asio.timer"; } + + std::string message(int value) const { + return value == error::timeout + ? "Operation timed out" + : "asio.timer error"; + } + }; + + } // namespace detail + + inline const boost::system::error_category& get_timer_category() { + static detail::timer_category instance; + return instance; + } + + inline boost::system::error_code make_error_code(boost::asio::error::timer_errors e) { + return boost::system::error_code( + static_cast(e), boost::asio::error::get_timer_category()); + } + +} // namespace error +} // namespace asio + +namespace system { + + template<> struct is_error_code_enum { + static const bool value = true; + }; + +} // namespace system +} // namespace boost + +#endif // _EIXX_TIMEOUT_HPP_ diff --git a/license.sh b/license.sh deleted file mode 100644 index efd5e54..0000000 --- a/license.sh +++ /dev/null @@ -1,4 +0,0 @@ -for f in $(find include -name '*.?pp') $(find src -name '*.cpp'); do - awk 'BEGIN {m=1} /BEGIN LICENSE BLOCK/ {m=0; print $0; while (getline < "LICENSE.header") print} ' \ - '/END LICENSE BLOCK/ {m=1} m {print $0}' $f > t && mv -v t $f -done diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..855c26b --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,69 @@ +# vim:ts=2:sw=2:et + +list(APPEND EIXX_SRCS + am.cpp + config.cpp +) + +if (NOT EIXX_MARSHAL_ONLY) + list(APPEND EIXX_SRCS + basic_otp_node_local.cpp + ) +endif() + +add_library(${PROJECT_NAME} SHARED ${EIXX_SRCS}) +add_library(${PROJECT_NAME}_static STATIC ${EIXX_SRCS}) +set_target_properties(${PROJECT_NAME}_static PROPERTIES OUTPUT_NAME ${PROJECT_NAME}) +target_link_libraries(${PROJECT_NAME} ${EIXX_LIBS}) + +if (${CMAKE_BUILD_TYPE} STREQUAL "debug") + set_target_properties(${PROJECT_NAME} PROPERTIES + DEBUG_POSTFIX "${LIB_SUFFIX}" + RELEASE_POSTFIX "") + set_target_properties(${PROJECT_NAME}_static PROPERTIES + DEBUG_POSTFIX "${LIB_SUFFIX}" + RELEASE_POSTFIX "") +endif() + +set_target_properties(${PROJECT_NAME} PROPERTIES VERSION ${PROJECT_VERSION}) + +if (NOT EIXX_MARSHAL_ONLY) + add_executable(test-node test_node.cpp) + target_link_libraries(${PROJECT_NAME} ${Boost_LIBRARIES}) + target_link_libraries(test-node eixx pthread) +endif() + +# In the install below we split library installation in a separate library clause +# so that it's possible to build/install both Release and Debug versions of the +# library and then include that into a package + +install( + TARGETS + ${PROJECT_NAME} + ${PROJECT_NAME}_static + LIBRARY DESTINATION lib + ARCHIVE DESTINATION lib +) + +if (${CMAKE_BUILD_TYPE} STREQUAL "release") + install( + FILES ${CMAKE_BINARY_DIR}/src/lib${PROJECT_NAME}_d.so.${PROJECT_VERSION} + DESTINATION lib + ) + install( + FILES ${CMAKE_BINARY_DIR}/src/lib${PROJECT_NAME}_d.so + DESTINATION lib + ) + install( + FILES ${CMAKE_BINARY_DIR}/src/lib${PROJECT_NAME}_d.a + DESTINATION lib + ) +endif() + +if (NOT EIXX_MARSHAL_ONLY) + install( + TARGETS + test-node + RUNTIME DESTINATION test + ) +endif() diff --git a/src/Makefile.am b/src/Makefile.am deleted file mode 100644 index 1b0718f..0000000 --- a/src/Makefile.am +++ /dev/null @@ -1,91 +0,0 @@ -eixx_LTLIBRARIES = libeixx.la -bin_PROGRAMS = test_node - -eixx_hdrs1 = alloc_pool.hpp \ - alloc_pool_st.hpp \ - alloc_std_debug.hpp \ - alloc_std.hpp \ - config.h \ - connect.hpp \ - eixx.hpp \ - eterm.hpp \ - eterm_exception.hpp -eixx_hdrs2 = connect/basic_otp_connection.hpp \ - connect/basic_otp_mailbox.hpp \ - connect/basic_otp_mailbox_registry.hpp \ - connect/basic_otp_node.hpp \ - connect/basic_otp_node.ipp \ - connect/basic_otp_node_local.hpp \ - connect/test_helper.hpp \ - connect/transport_msg.hpp \ - connect/transport_otp_connection.hpp \ - connect/transport_otp_connection.ipp \ - connect/transport_otp_connection_tcp.hpp \ - connect/transport_otp_connection_tcp.ipp \ - connect/transport_otp_connection_uds.hpp \ - connect/verbose.hpp -eixx_hdrs3 = marshal/alloc_base.hpp \ - marshal/atom.hpp \ - marshal/binary.hpp \ - marshal/binary.ipp \ - marshal/defaults.hpp \ - marshal/endian.hpp \ - marshal/eterm_format.hpp \ - marshal/eterm_format.ipp \ - marshal/eterm.hpp \ - marshal/eterm.ipp \ - marshal/eterm_match.hpp \ - marshal/list.hpp \ - marshal/list.ipp \ - marshal/pid.hpp \ - marshal/pid.ipp \ - marshal/port.hpp \ - marshal/port.ipp \ - marshal/ref.hpp \ - marshal/ref.ipp \ - marshal/string.hpp \ - marshal/trace.hpp \ - marshal/tuple.hpp \ - marshal/tuple.ipp \ - marshal/varbind.hpp \ - marshal/var.hpp \ - marshal/visit_encoder.hpp \ - marshal/visit_encode_size.hpp \ - marshal/visit.hpp \ - marshal/visit_match.hpp \ - marshal/visit_subst.hpp \ - marshal/visit_to_string.hpp -eixx_hdrs4 = util/async_wait_timeout.hpp \ - util/common.hpp \ - util/hashtable.hpp \ - util/string_util.hpp \ - util/sync.hpp - -eixx_headers= $(eixx_hdrs1) \ - $(eixx_hdrs2) \ - $(eixx_hdrs3) \ - $(eixx_hdrs4) - -libeixx_la_hdr1dir = $(includedir)/@PACKAGE@ -libeixx_la_hdr1_HEADERS = $(eixx_hdrs1:%=../include/@PACKAGE@/%) -libeixx_la_hdr2dir = $(includedir)/@PACKAGE@/connect -libeixx_la_hdr2_HEADERS = $(eixx_hdrs2:%=../include/@PACKAGE@/%) -libeixx_la_hdr3dir = $(includedir)/@PACKAGE@/marshal -libeixx_la_hdr3_HEADERS = $(eixx_hdrs3:%=../include/@PACKAGE@/%) -libeixx_la_hdr4dir = $(includedir)/@PACKAGE@/util -libeixx_la_hdr4_HEADERS = $(eixx_hdrs4:%=../include/@PACKAGE@/%) - -libeixx_la_SOURCES = atom.cpp defaults.cpp basic_otp_node_local.cpp \ - $(eixx_headers:%=../include/@PACKAGE@/%) -libeixx_la_LTADD = -version-info 0:1 -shared -libeixx_la_LIBS = $(BOOST_THREAD_LIB) $(CRYPTO_LIBS) -libeixx_la_CXXFLAGS = $(if $(tr1),-std=c++0x) -g -O$(if $(optimize),3 -DBOOST_DISABLE_ASSERTS,0) -I../include $(BOOST_CPPFLAGS) \ - $(EI_CPPFLAGS) - -#EXTRA_DIST = ../README overview.inl overview_usage.inl overview_config.inl - -test_node_SOURCES = test_node.cpp -test_node_CPPFLAGS = -g -O0 -I$(srcdir)/../include $(BOOST_CPPFLAGS) $(EI_CPPFLAGS) -test_node_LDFLAGS = $(BOOST_LDFLAGS) $(EI_LDFLAGS) -test_node_LDADD = libeixx.la $(EI_LIB) \ - $(BOOST_SYSTEM_LIB) $(BOOST_THREAD_LIB) $(CRYPTO_LIBS) diff --git a/src/am.cpp b/src/am.cpp new file mode 100644 index 0000000..a7fb48f --- /dev/null +++ b/src/am.cpp @@ -0,0 +1,50 @@ +/* +***** BEGIN LICENSE BLOCK ***** + +Copyright 2010 Serge Aleynikov + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +***** END LICENSE BLOCK ***** +*/ + +#include +#include + +namespace eixx { + + const atom am_badarg = atom("badarg"); + const atom am_badrpc = atom("badrpc"); + const atom am_call = atom("call"); + const atom am_cast = atom("cast"); + const atom am_erlang = atom("erlang"); + const atom am_error = atom("error"); + const atom am_false = atom("false"); + const atom am_format = atom("format"); + const atom am_gen_cast = atom("$gen_cast"); + const atom am_io_lib = atom("io_lib"); + const atom am_latin1 = atom("latin1"); + const atom am_noconnection = atom("noconnection"); + const atom am_noproc = atom("noproc"); + const atom am_normal = atom("normal"); + const atom am_ok = atom("ok"); + const atom am_request = atom("request"); + const atom am_rex = atom("rex"); + const atom am_rpc = atom("rpc"); + const atom am_true = atom("true"); + const atom am_undefined = atom("undefined"); + const atom am_unsupported = atom("unsupported"); + const atom am_user = atom("user"); + +} // namespace eixx + diff --git a/src/atom.cpp b/src/atom.cpp deleted file mode 100644 index 5c09439..0000000 --- a/src/atom.cpp +++ /dev/null @@ -1,16 +0,0 @@ -#include -#include -//#include - -namespace EIXX_NAMESPACE { -namespace marshal { - - detail::atom_table<>& atom::atom_table() { - //return boost::details::pool::singleton_default >::instance(); - static detail::atom_table<> s_atom_table; - return s_atom_table; - } - -} // namespace marshal -} // namespace EIXX_NAMESPACE - diff --git a/src/basic_otp_node_local.cpp b/src/basic_otp_node_local.cpp index 38e0651..19c6043 100644 --- a/src/basic_otp_node_local.cpp +++ b/src/basic_otp_node_local.cpp @@ -1,26 +1,23 @@ /* ***** BEGIN LICENSE BLOCK ***** -This file is part of the EPI (Erlang Plus Interface) Library. +Copyright 2010 Serge Aleynikov -Copyright (C) 2005 Hector Rivas Gandara +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. + http://www.apache.org/licenses/LICENSE-2.0 -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. ***** END LICENSE BLOCK ***** */ +#include #include #include @@ -29,11 +26,11 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include #include -namespace EIXX_NAMESPACE { +namespace eixx { namespace connect { namespace { - std::string get_default_cookie() { + atom get_default_cookie() { const char* home = getenv("HOME") ? getenv("HOME") : ""; if (home) { std::stringstream s; @@ -50,33 +47,33 @@ namespace { file >> cookie; size_t n = cookie.find('\n'); if (n != std::string::npos) cookie.erase(n); - return cookie; + if (cookie.size() > EI_MAX_COOKIE_SIZE) + throw err_bad_argument("Cookie size too long", cookie.size()); + return atom(cookie); } } - return "no_cookie"; + return atom(); } } // namespace -std::string basic_otp_node_local::s_default_cookie = get_default_cookie(); +atom basic_otp_node_local::s_default_cookie = get_default_cookie(); std::string basic_otp_node_local::s_localhost = boost::asio::ip::host_name(); basic_otp_node_local::basic_otp_node_local( const std::string& a_nodename, const std::string& a_cookie) - throw (std::runtime_error, err_bad_argument) { set_nodename(a_nodename, a_cookie); } void basic_otp_node_local::set_nodename( const std::string& a_nodename, const std::string& a_cookie) - throw (std::runtime_error, err_bad_argument) { - m_cookie = a_cookie.empty() ? s_default_cookie : a_cookie; - if (m_cookie.size() > EI_MAX_COOKIE_SIZE) throw err_bad_argument("Cookie size too long", m_cookie.size()); + m_cookie = a_cookie.empty() ? s_default_cookie : atom(a_cookie); + std::string::size_type pos = a_nodename.find('@'); if (pos == std::string::npos) { @@ -88,10 +85,15 @@ void basic_otp_node_local::set_nodename( } std::string short_hostname; + boost::system::error_code ec; + boost::asio::ip::address::from_string( m_hostname, ec ); pos = m_hostname.find('.'); - std::stringstream str; - if (pos != std::string::npos) { + + if(! ec) { + str << m_alivename << '@' << m_hostname; + m_longname = m_alivename+'@'+m_hostname; + } else if (pos != std::string::npos) { str << m_alivename << '@' << m_hostname.substr(0, pos); m_longname = m_alivename+'@'+m_hostname; } else { @@ -119,4 +121,4 @@ void basic_otp_node_local::set_nodename( } } // namespace connect -} // namespace EIXX_NAMESPACE +} // namespace eixx diff --git a/src/config.cpp b/src/config.cpp new file mode 100644 index 0000000..b41a346 --- /dev/null +++ b/src/config.cpp @@ -0,0 +1,36 @@ +//---------------------------------------------------------------------------- +/// \file config.hpp +//---------------------------------------------------------------------------- +/// \brief A class encapsulation static configuration options +//---------------------------------------------------------------------------- +// Copyright (c) 2021 Serge Aleynikov +// Created: 2021-08-25 +//---------------------------------------------------------------------------- +/* +***** BEGIN LICENSE BLOCK ***** + +Copyright 2010 Serge Aleynikov + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +***** END LICENSE BLOCK ***** +*/ + +#include + +namespace eixx { +namespace marshal { + +bool config::s_display_creation = true; + +}} // eixx::marshal diff --git a/src/defaults.cpp b/src/defaults.cpp deleted file mode 100644 index 2f88050..0000000 --- a/src/defaults.cpp +++ /dev/null @@ -1,25 +0,0 @@ -#include - -namespace EIXX_NAMESPACE { - - const char* type_to_string(eterm_type a_type) { - switch (a_type) { - case LONG : return "LONG"; - case DOUBLE: return "DOUBLE"; - case BOOL : return "BOOL"; - case ATOM : return "ATOM"; - case STRING: return "STRING"; - case BINARY: return "BINARY"; - case PID : return "PID"; - case PORT : return "PORT"; - case REF : return "REF"; - case VAR : return "VAR"; - case TUPLE : return "TUPLE"; - case LIST : return "LIST"; - case TRACE : return "TRACE"; - default : return "UNDEFINED"; - } - } - -} // namespace EIXX_NAMESPACE - diff --git a/src/eixx.pc.in b/src/eixx.pc.in deleted file mode 100644 index 76b4e39..0000000 --- a/src/eixx.pc.in +++ /dev/null @@ -1,12 +0,0 @@ -prefix=@prefix@ -exec_prefix=@exec_prefix@ -libdir=@libdir@ -includedir=@includedir@ - -Name: @PACKAGE@ -Description: EIXX: C++ Interface to Erlang -#Requires: boost_0_41_0 -Version: @PACKAGE_VERSION@ -Libs: -L${libdir} @EI_LDFLAGS@ -leixx @EI_LIB@ @CRYPTO_LIBS@ -Cflags: -I${includedir} @EI_CPPFLAGS@ - diff --git a/src/server.hpp b/src/server.hpp index 61ab3c2..8d769c9 100644 --- a/src/server.hpp +++ b/src/server.hpp @@ -8,15 +8,16 @@ // Created: 2010-04-11 // -#ifndef _SERVER_HPP_ -#define _SERVER_HPP_ +#pragma once + +#include #include #include #include #include "channel.hpp" -namespace perc { +namespace eixx { //------------------------------------------------------------------------------ // channel_manager class @@ -28,7 +29,7 @@ template class channel_manager : private boost::noncopyable { public: - typedef boost::shared_ptr channel_ptr; + using channel_ptr = boost::shared_ptr; /// Add the specified channel to the manager and start it. void start(channel_ptr c, bool a_connected = false) { @@ -46,7 +47,7 @@ class channel_manager : private boost::noncopyable /// Stop all channels. void stop_all() { std::for_each(m_channels.begin(), m_channels.end(), - boost::bind(&Connection::stop, _1)); + std::bind(&Connection::stop, _1)); m_channels.clear(); } @@ -69,8 +70,8 @@ template class server : private boost::noncopyable { public: - typedef Handler handler_type; - typedef boost::shared_ptr > pointer; + using handler_type = Handler; + using pointer = boost::shared_ptr>; /// Construct the server to listen on the specified TCP address and port, and /// serve up files from the given directory. @@ -129,7 +130,7 @@ class server : private boost::noncopyable virtual void stop() { // Post a call to the stop function so that server::stop() is safe to call // from any thread. - m_io_service.post(boost::bind(&server::handle_stop, this)); + m_io_service.post(std::bind(&server::handle_stop, this)); } // Event handlers @@ -222,7 +223,7 @@ class tcp_server : public server< Handler > m_acceptor.listen(); m_acceptor.async_accept( static_cast&>(*m_new_channel).socket(), - boost::bind(&tcp_server::handle_accept, + std::bind(&tcp_server::handle_accept, this, boost::asio::placeholders::error)); } @@ -241,7 +242,7 @@ class tcp_server : public server< Handler > m_new_channel->handler(), channel::TCP); m_acceptor.async_accept( static_cast&>(*m_new_channel).socket(), - boost::bind(&tcp_server::handle_accept, + std::bind(&tcp_server::handle_accept, this, boost::asio::placeholders::error)); } } @@ -303,7 +304,7 @@ class uds_server : public server< Handler > // Open the acceptor m_acceptor.async_accept( static_cast&>(*m_new_channel).socket(), - boost::bind(&uds_server::handle_accept, + std::bind(&uds_server::handle_accept, this, boost::asio::placeholders::error)); } @@ -322,7 +323,7 @@ class uds_server : public server< Handler > m_new_channel->handler(), channel::UDS); m_acceptor.async_accept( static_cast&>(*m_new_channel).socket(), - boost::bind(&uds_server::handle_accept, + std::bind(&uds_server::handle_accept, this, boost::asio::placeholders::error)); } } @@ -337,6 +338,4 @@ class uds_server : public server< Handler > } }; -} // namespace perc - -#endif // _SERVER_HPP_ +} // namespace eixx diff --git a/src/test_node.cpp b/src/test_node.cpp index e49f77b..c88c66d 100644 --- a/src/test_node.cpp +++ b/src/test_node.cpp @@ -1,13 +1,13 @@ #include #include +#include #include -#include #define BOOST_REQUIRE #include -using namespace EIXX_NAMESPACE; +using namespace eixx; void usage(char* exe) { printf("Usage: %s -n NODE -r REMOTE_NODE [-c COOKIE] [-v VERBOSE] [-t RECONNECT_SECS]\n" @@ -18,80 +18,22 @@ void usage(char* exe) { exit(1); } -void on_status(otp_node& a_node, const otp_connection* a_con, +void on_status([[maybe_unused]] otp_node& a_node, [[maybe_unused]] const otp_connection* a_con, report_level a_level, const std::string& s) { static const char* s_levels[] = {"INFO ", "WARNING", "ERROR "}; std::cerr << s_levels[a_level] << "| " << s << std::endl; } -otp_mailbox *g_io_server, *g_main; -static atom g_rem_node; +boost::shared_ptr g_io_server; +boost::shared_ptr g_main; -void on_io_request(otp_mailbox& a_mbox, boost::system::error_code& ec) { - if (ec == boost::asio::error::operation_aborted) { - eixx::transport_msg* p; - while ((p = a_mbox.receive()) != NULL) { - boost::scoped_ptr l_tmsg(p); - - static eterm s_put_chars = eterm::format("{io_request,_,_,{put_chars,S}}"); - - varbind l_binding; - if (s_put_chars.match(l_tmsg->msg(), &l_binding)) - std::cerr << "I/O request from server: " - << l_binding["S"]->to_string() << std::endl; - else - std::cerr << "I/O server got a message: " << l_tmsg->msg() << std::endl; - } - } else if (ec != boost::asio::error::timeout) - return; - - a_mbox.async_receive(&on_io_request); -} +static atom g_rem_node; -void on_main_msg(otp_mailbox& a_mbox, boost::system::error_code& ec) { - if (ec == boost::asio::error::operation_aborted) { - eixx::transport_msg* p; - while ((p = a_mbox.receive()) != NULL) { - boost::scoped_ptr l_tmsg(p); - - const eterm& l_msg = l_tmsg->msg(); - - static eterm s_now_pattern = eterm::format("{rex, {N1, N2, N3}}"); - static eterm s_stop = atom("stop"); - - varbind l_binding; - - if (s_now_pattern.match(l_msg, &l_binding)) { - struct timeval tv = - { l_binding["N1"]->to_long() * 1000000 + - l_binding["N1"]->to_long(), - l_binding["N1"]->to_long() }; - struct tm tm; - localtime_r(&tv.tv_sec, &tm); - printf("Server time: %02d:%02d:%02d.%06ld\n", - tm.tm_hour, tm.tm_min, tm.tm_sec, tv.tv_usec); - } else if (s_stop.match(l_msg)) { - a_mbox.node().stop(); - return; - } else - std::cout << "Unhandled message: " << l_msg << std::endl; - } - } else if (ec == boost::asio::error::timeout) { - // Make sure that remote node has a process registered as "test". - // Try sending a message to it. - static uint32_t n; - if (n++ & 1) - g_main->send_rpc(g_rem_node, "erlang", "now", list::make()); - else - g_io_server->send_rpc_cast(g_rem_node, atom("io"), atom("put_chars"), - list::make("This is a test string"), &g_io_server->self()); - } else if (ec) { - return; - } - - a_mbox.async_receive(&on_main_msg, 5000); -} +static const atom S = atom("S"); +static const atom N1 = atom("N1"); +static const atom N2 = atom("N2"); +static const atom N3 = atom("N3"); void on_connect(otp_connection* a_con, const std::string& a_error) { if (!a_error.empty()) { @@ -99,37 +41,105 @@ void on_connect(otp_connection* a_con, const std::string& a_error) { return; } + if (g_rem_node != a_con->remote_nodename()) + throw eixx::err_connection("Connection from the wrong node: " + + a_con->remote_nodename().to_string()); + // Make sure that remote node has a process registered as "test". // Try sending a message to it. - g_main->send_rpc(a_con->remote_node(), "erlang", "now", list::make()); + g_main->send_rpc(a_con->remote_nodename(), "erlang", "now", list::make()); // Send an rpc request to print a string. The remote - g_io_server->send_rpc_cast(a_con->remote_node(), atom("io"), atom("put_chars"), + g_io_server->send_rpc_cast(a_con->remote_nodename(), atom("io"), atom("put_chars"), list::make("This is a test string"), &g_io_server->self()); + + g_io_server->send_rpc_cast(a_con->remote_nodename(), atom("io"), atom("put_chars"), + list::make("DONE"), &g_io_server->self()); } void on_disconnect( otp_node& a_node, const otp_connection& a_con, - const std::string& a_remote_node, const boost::system::error_code& err) + atom a_remote_node, [[maybe_unused]] const boost::system::error_code& err) { std::cout << "Disconnected from remote node " << a_remote_node << std::endl; if (a_con.reconnect_timeout() == 0) a_node.stop(); } +// Messages to the 'main' mailbox +bool on_msg(otp_mailbox& a_mbox, eixx::transport_msg*& a_msg) +{ + static const eterm s_now_pattern = eterm::format("{rex, {N1, N2, N3}}"); + static const eterm s_stop = atom("stop"); + + if (!a_msg) + return true; + + varbind l_binding; + + const eterm& l_msg = a_msg->msg(); + + if (l_msg.match(s_now_pattern, &l_binding)) { + struct timeval tv = + { l_binding[N1]->to_long() * 1000000 + + l_binding[N2]->to_long(), + static_cast(l_binding[N3]->to_long()) }; + struct tm tm; + localtime_r(&tv.tv_sec, &tm); + printf( +#ifdef __APPLE__ + "Server time: %02d:%02d:%02d.%06d\n", +#else + "Server time: %02d:%02d:%02d.%06ld\n", +#endif + tm.tm_hour, tm.tm_min, tm.tm_sec, tv.tv_usec); + + std::cout << "Sending DONE message" << std::endl; + g_io_server->send_rpc_cast(g_rem_node, "io", "put_chars", + list::make("DONE"), &g_io_server->self()); + } else if (l_msg.match(s_stop)) { + a_mbox.node().stop(); + return false; + } else + std::cout << "Unhandled message: " << l_msg << std::endl; + + return true; +} + +bool on_io(otp_mailbox& a_mbox, eixx::transport_msg*& a_msg) +{ + static const eterm s_put_chars = eterm::format("{io_request,_,_,{put_chars,S}}"); + + if (!a_msg) + return true; + + varbind l_binding; + if (s_put_chars.match(a_msg->msg(), &l_binding)) { + std::cerr << "I/O request from server: " + << l_binding[S]->to_string() << std::endl; + auto s = l_binding[S]->to_string(); + if (s == "<<\"DONE\">>") + a_mbox.node().stop(); + } + else + std::cerr << "I/O server got a message: " << a_msg->msg() << std::endl; + + return true; +} + int main(int argc, char* argv[]) { if (argc < 2) usage(argv[0]); - - const char* l_nodename = NULL, *l_remote = NULL, *use_cookie = ""; + + const char* nodename = NULL, *remote = NULL, *use_cookie = ""; connect::verbose_type verbose = connect::verboseness::level(); int reconnect_secs = 0; for (int i = 1; i < argc && argv[i][0] == '-'; i++) { if (strcmp(argv[i], "-n") == 0 && i < argc-1) - l_nodename = argv[++i]; + nodename = argv[++i]; else if (strcmp(argv[i], "-r") == 0 && i < argc-1) - l_remote = argv[++i]; + remote = argv[++i]; else if (strcmp(argv[i], "-c") == 0 && i < argc-1) use_cookie = argv[++i]; else if (strcmp(argv[i], "-v") == 0 && i < argc-1) @@ -138,31 +148,33 @@ int main(int argc, char* argv[]) { reconnect_secs = atoi(argv[++i]); else usage(argv[0]); - } + } - if (!l_nodename || !l_remote) + if (!nodename || !remote) usage(argv[0]); boost::asio::io_service io_service; - otp_node l_node(io_service, l_nodename, use_cookie); - l_node.verbose(verbose); - l_node.on_status = on_status; - l_node.on_disconnect = on_disconnect; + otp_node node(io_service, nodename, use_cookie); + node.verbose(verbose); + node.on_status = on_status; + node.on_disconnect = on_disconnect; + + g_io_server.reset(node.create_mailbox("io_server")); + g_main .reset(node.create_mailbox("main")); + g_rem_node = atom(remote); - g_io_server = l_node.create_mailbox("io_server"); - g_main = l_node.create_mailbox("main"); - g_rem_node = atom(l_remote); + node.connect(on_connect, g_rem_node, reconnect_secs); - l_node.connect(&on_connect, l_remote, reconnect_secs); + //otp_connection::connection_type* transport = a_con->transport(); + g_io_server->async_receive(on_io, + // IO Request + std::chrono::milliseconds(-1), -1); - //otp_connection::connection_type* l_transport = a_con->transport(); - g_io_server->async_receive(&on_io_request); + //node.send_rpc(self, g_rem_node, atom("shell_default"), atom("ls"), + // list::make(), &g_io_server); + g_main->async_receive(on_msg, std::chrono::seconds(5), -1); - //l_node->send_rpc(self, a_con->remote_node(), atom("shell_default"), atom("ls"), - // list::make(), &io_server); - g_main->async_receive(&on_main_msg, 5000); + node.run(); - l_node.run(); - return 0; } diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 0000000..a0fed64 --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,81 @@ +# vim:ts=2:sw=2:et + +list(APPEND TEST_SRCS + test_eterm.cpp + test_eterm_encode.cpp + test_eterm_format.cpp + test_eterm_match.cpp + test_eterm_pool.cpp + test_eterm_refc.cpp +) + +if (NOT EIXX_MARSHAL_ONLY) + list(APPEND TEST_SRCS + test_mailbox.cpp + test_node.cpp + ) +endif() + +# This must be AFTER link_directories +add_executable(test-eterm ${TEST_SRCS}) +target_compile_definitions(test-eterm PRIVATE -DBOOST_TEST_DYN_LINK) +target_include_directories(test-eterm PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) +target_link_libraries( + test-eterm + boost_unit_test_framework + eixx + ${Erlang_EI_LIBRARIES} + ${Boost_LIBRARIES} +) + +if(CMAKE_SYSTEM_NAME STREQUAL Linux OR + CMAKE_SYSTEM_NAME STREQUAL Android) + add_executable(test-perf test_perf.cpp) + target_link_libraries( + test-perf + eixx + ${Erlang_EI_LIBRARIES} + ${Boost_LIBRARIES} + ) +endif() + +if (NOT EIXX_MARSHAL_ONLY) + add_executable(test-connect test_async_queue.cpp) + target_link_libraries( + test-connect + boost_unit_test_framework + eixx + ${Erlang_EI_LIBRARIES} + ${Boost_LIBRARIES} + ) + + install( + TARGETS + test-eterm + RUNTIME DESTINATION test + ) + + if(CMAKE_SYSTEM_NAME STREQUAL Linux OR + CMAKE_SYSTEM_NAME STREQUAL Android) + install( + TARGETS + test-perf + RUNTIME DESTINATION test + ) + endif() +endif() + +install( + TARGETS + test-eterm + RUNTIME DESTINATION test +) + +if(CMAKE_SYSTEM_NAME STREQUAL Linux OR + CMAKE_SYSTEM_NAME STREQUAL Android) + install( + TARGETS + test-perf + RUNTIME DESTINATION test + ) +endif() diff --git a/test/Makefile.am b/test/Makefile.am deleted file mode 100644 index e79e43a..0000000 --- a/test/Makefile.am +++ /dev/null @@ -1,24 +0,0 @@ -bin_PROGRAMS = test_eterm test_perf - -AM_CXXFLAGS = -I. -I$(srcdir)/../include $(BOOST_CPPFLAGS) \ - -I$(ERLANG_LIB_DIR_erl_interface)/include \ - -I$(ERLANG_LIB_DIR_erl_interface)/src - -test_eterm_SOURCES = test_eterm.cpp test_eterm_encode.cpp \ - test_eterm_format.cpp test_eterm_match.cpp \ - test_eterm_pool.cpp test_eterm_refc.cpp \ - test_mailbox.cpp test_node.cpp -test_eterm_CPPFLAGS = -DBOOST_TEST_DYN_LINK -g -O$(if $(optimize),3 -DBOOST_DISABLE_ASSERTS,0) \ - $(if $(debug),-DEIXX_DEBUG) -test_eterm_LDFLAGS = $(BOOST_LDFLAGS) \ - -L$(ERLANG_LIB_DIR_erl_interface)/lib -test_eterm_LDADD = -L../src/.libs -leixx -lei \ - $(BOOST_SYSTEM_LIB) $(BOOST_UNIT_TEST_FRAMEWORK_LIB) \ - $(BOOST_THREAD_LIB) - -test_perf_SOURCES = test_perf.cpp -test_perf_CPPFLAGS = -O$(if $(optimize),3 -DBOOST_DISABLE_ASSERTS,0) -test_perf_LDFLAGS = -L$(ERLANG_LIB_DIR_erl_interface)/lib $(BOOST_LDFLAGS) -test_perf_LDADD = -L../src/.libs -leixx $(BOOST_SYSTEM_LIB) - - diff --git a/test/test_async_queue.cpp b/test/test_async_queue.cpp new file mode 100644 index 0000000..0544a5c --- /dev/null +++ b/test/test_async_queue.cpp @@ -0,0 +1,173 @@ +//---------------------------------------------------------------------------- +/// \file test_mailbox.cpp +//---------------------------------------------------------------------------- +/// \brief Test cases for basic_otp_mailbox. +//---------------------------------------------------------------------------- +// Copyright (c) 2010 Serge Aleynikov +// Created: 2010-09-23 +//---------------------------------------------------------------------------- +/* +***** BEGIN LICENSE BLOCK ***** + +Copyright 2010 Serge Aleynikov + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +***** END LICENSE BLOCK ***** +*/ + +#define BOOST_TEST_MODULE test_async_queue + +//#define BOOST_ASIO_ENABLE_HANDLER_TRACKING + +#include +#include +#include +#include +#include +#include + +using namespace eixx::util; + +int b, n, i; + +BOOST_AUTO_TEST_CASE( test_async_queue ) +{ + boost::asio::io_service io; + + std::shared_ptr> q(new async_queue(io)); + + bool res = q->async_dequeue( + [](int& /*a*/, const boost::system::error_code& /*ec*/) { + throw std::exception(); // This handler is never called + return false; + }, + std::chrono::milliseconds(0)); + + BOOST_REQUIRE(res); // Returns true because there was no async dispatch + + for (i = 10; i < 13; i++) + BOOST_REQUIRE(q->enqueue(i)); + + for (int j = 10; j < 13; j++) { + BOOST_REQUIRE(q->async_dequeue( + [j](int& a, const boost::system::error_code& /*ec*/) { + BOOST_REQUIRE_EQUAL(j, a); + return true; + }, + std::chrono::milliseconds(0))); + } + + b = 15; + bool r = q->async_dequeue( + [](int& a, const boost::system::error_code& /*ec*/) { + BOOST_REQUIRE_EQUAL(b++, a); + n++; + return true; + }, 3); + BOOST_REQUIRE(!r); + + i = 15; + + BOOST_REQUIRE(q->enqueue(i++)); + BOOST_REQUIRE(q->enqueue(i++)); + BOOST_REQUIRE(q->enqueue(i++)); + + io.run(); + + BOOST_REQUIRE_EQUAL(3, n); + BOOST_REQUIRE_EQUAL(18, i); + BOOST_REQUIRE_EQUAL(18, b); +} + +namespace { + const int iterations = getenv("ITERATIONS") ? atoi(getenv("ITERATIONS")) : 1000000; + + std::atomic_int producer_count(0); + std::atomic_int consumer_count(0); + + const int producer_thread_count = 4; + std::atomic_int done_producer_count(0); + + std::atomic done (false); +} + +void producer(async_queue& q, int id) +{ + for (int idx = 0; idx != iterations; ++idx) { + int value = ++producer_count; + while (!q.enqueue(value)); + } + + if (eixx::verboseness::level() >= eixx::connect::VERBOSE_DEBUG) + std::cout << "Producer thread " << id << " done" << std::endl; + + done = ++done_producer_count == producer_thread_count; +} + +BOOST_AUTO_TEST_CASE( test_async_queue_concurrent ) +{ + boost::asio::io_service io; + + boost::shared_ptr> q(new async_queue(io, 128)); + + boost::thread_group producer_threads; + + for (int idx = 0; idx < producer_thread_count; ++idx) + producer_threads.create_thread( + [&q, idx] () { producer(*q, idx+1); } + ); + + while(q->async_dequeue( + [] (int& /*v*/, const boost::system::error_code& ec) { + if (!ec) + ++consumer_count; + return !(done && consumer_count >= producer_count); + }, + std::chrono::milliseconds(1000), + -1)); + + io.run(); + io.reset(); + + producer_threads.join_all(); + + if (eixx::verboseness::level() >= eixx::connect::VERBOSE_DEBUG) { + std::cout << "Produced " << producer_count << " objects." << std::endl; + std::cout << "Consumed " << consumer_count << " objects." << std::endl; + } + + bool abort = false; + + auto r = q->async_dequeue( + [&abort] (int& /*v*/, const boost::system::error_code& /*ec*/) { return !abort; }, + std::chrono::milliseconds(8000), + -1); + + BOOST_REQUIRE(!r); + + boost::asio::system_timer t(io); + t.expires_from_now(std::chrono::milliseconds(1)); + t.async_wait([&q, &abort](const boost::system::error_code& /*e*/) { + q->cancel(); + abort = true; + if (eixx::verboseness::level() >= eixx::connect::VERBOSE_DEBUG) + std::cout << "Canceled timer" << std::endl; + }); + + io.run(); + + if (eixx::verboseness::level() >= eixx::connect::VERBOSE_DEBUG) + std::cout << "Done!" << std::endl; +} + diff --git a/test/test_eterm.cpp b/test/test_eterm.cpp index a27d0b8..c4f5de7 100644 --- a/test/test_eterm.cpp +++ b/test/test_eterm.cpp @@ -1,77 +1,126 @@ /* ***** BEGIN LICENSE BLOCK ***** -This file is part of the EPI (Erlang Plus Interface) Library. +Copyright 2010 Serge Aleynikov -Copyright (C) 2010 Serge Aleynikov +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. + http://www.apache.org/licenses/LICENSE-2.0 -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. ***** END LICENSE BLOCK ***** */ #define BOOST_TEST_MODULE test_eterm -#include +#include #include "test_alloc.hpp" #include #include +#include using namespace eixx; +BOOST_AUTO_TEST_CASE( test_type ) +{ + { + eterm t(10); + BOOST_CHECK_EQUAL(10, t.get()); + BOOST_CHECK_EQUAL(10, t.get()); + BOOST_CHECK_EQUAL(10, t.get()); + BOOST_CHECK_EQUAL(10, t.get()); + BOOST_CHECK_EQUAL(10, t.get()); + BOOST_CHECK_EQUAL(10, t.get()); + BOOST_CHECK_THROW(t.get(), err_wrong_type); + } + { + eterm t(10.0); + BOOST_CHECK_EQUAL(10.0, t.get()); + BOOST_CHECK_THROW(t.get(), err_wrong_type); + } + { + eterm t(true); + BOOST_CHECK_EQUAL(true, t.get()); + BOOST_CHECK_THROW(t.get(), err_wrong_type); + } + { + eterm t("ABC"); + BOOST_CHECK_EQUAL("ABC", t.get()); + BOOST_CHECK_THROW(t.get(), err_wrong_type); + } +} + BOOST_AUTO_TEST_CASE( test_atomable ) { - { - marshal::detail::atom_table<> t(10); - BOOST_REQUIRE_EQUAL(0u, t.lookup("")); - BOOST_REQUIRE_EQUAL(1u, t.lookup("abc")); - BOOST_REQUIRE_EQUAL(2u, t.lookup("aaaaa")); - BOOST_REQUIRE_EQUAL(1u, t.lookup("abc")); - } + util::atom_table t(10); + BOOST_CHECK_EQUAL(0, t.lookup(std::string())); + BOOST_CHECK_EQUAL(0, t.lookup("")); + auto n = t.lookup("abc"); + BOOST_CHECK(0 < n); + BOOST_CHECK(0 < t.lookup("aaaaa")); + BOOST_CHECK_EQUAL(n, t.lookup("abc")); } BOOST_AUTO_TEST_CASE( test_atom ) { + { + EIXX_DECL_ATOM(temp); + BOOST_CHECK_EQUAL("temp", am_temp); + + EIXX_DECL_ATOM_VAR(am_temp2, "temp2"); + BOOST_CHECK_EQUAL("temp2", am_temp2); + } + { + auto p = util::atom_table().try_lookup("temp3"); + BOOST_CHECK_EQUAL(false, p.first); + BOOST_CHECK_EQUAL(1, p.second); + auto s = std::string(MAXATOMLEN+1, 'X'); + p = util::atom_table().try_lookup(s); + BOOST_CHECK_EQUAL(false, p.first); + BOOST_CHECK_EQUAL(2, p.second); + p = util::atom_table().try_lookup(""); + BOOST_CHECK_EQUAL(true, p.first); + BOOST_CHECK_EQUAL(0, p.second); + + BOOST_CHECK_THROW(atom("temp3", true), err_atom_not_found); + auto a = atom("temp3", false); + BOOST_CHECK_EQUAL("temp3", a); + } { atom a(""); - BOOST_REQUIRE_EQUAL(0u, a.index()); - BOOST_REQUIRE_EQUAL(atom(), a); + BOOST_CHECK_EQUAL(0, a.index()); + BOOST_CHECK_EQUAL(atom(), a); } { atom et1("Abc"); - BOOST_REQUIRE(et1.index() > 0); + BOOST_CHECK(et1.index() > 0); atom et2("aBc"); - BOOST_REQUIRE_NE(et1, et2); + BOOST_CHECK_NE(et1, et2); atom et3("Abc"); - BOOST_REQUIRE_EQUAL(et1, et3); - BOOST_REQUIRE_EQUAL(et1.index(), et3.index()); + BOOST_CHECK_EQUAL(et1, et3); + BOOST_CHECK_EQUAL(et1.index(), et3.index()); } { - const uint8_t buf[] = {ERL_ATOM_EXT,0,3,97,98,99}; - int i = 0; + const uint8_t buf[] = {ERL_ATOM_UTF8_EXT,0,3,97,98,99}; + uintptr_t i = 0; atom atom((const char*)buf, i, sizeof(buf)); - BOOST_REQUIRE_EQUAL(6, i); - BOOST_REQUIRE_EQUAL("abc", atom); - BOOST_REQUIRE_EQUAL(std::string("abc"), atom.c_str()); - BOOST_REQUIRE_EQUAL(atom.c_str(), std::string("abc")); + BOOST_CHECK_EQUAL(6, i); + BOOST_CHECK_EQUAL("abc", atom); + BOOST_CHECK_EQUAL(std::string("abc"), atom.c_str()); + BOOST_CHECK_EQUAL(atom.c_str(), std::string("abc")); eterm et1(atom); - BOOST_REQUIRE_EQUAL(std::string("abc"), et1.to_string()); + BOOST_CHECK_EQUAL(std::string("abc"), et1.to_string()); eterm et2(marshal::atom("Abc")); - BOOST_REQUIRE_EQUAL("'Abc'", et2.to_string()); + BOOST_CHECK_EQUAL("'Abc'", et2.to_string()); - BOOST_REQUIRE_EQUAL("a", et1.to_string(1)); + BOOST_CHECK_EQUAL("a", et1.to_string(1)); } } @@ -79,25 +128,42 @@ BOOST_AUTO_TEST_CASE( test_bool ) { allocator_t alloc; { + int n; eterm et(true); - BOOST_REQUIRE(et.initialized()); - BOOST_REQUIRE_EQUAL(BOOL, et.type()); - } - - { - const uint8_t buf[] = {ERL_ATOM_EXT,0,4,116,114,117,101}; - int i = 0; + BOOST_CHECK(et.initialized()); + BOOST_CHECK_EQUAL(BOOL, et.type()); + // Since the encode_size() functions for bool and double don't call + // ei's implementation but have hard-coded values inlined for efficiency, + // test that our assumption of the return matches the return of the + // corresponding ei's implementation: + BOOST_CHECK_EQUAL(6, marshal::visit_eterm_encode_size_calc()(true)); + BOOST_CHECK_EQUAL(7, marshal::visit_eterm_encode_size_calc()(false)); + BOOST_CHECK_EQUAL(9, marshal::visit_eterm_encode_size_calc()(0.0)); + n = 0; + BOOST_CHECK_EQUAL(0, ei_encode_boolean(NULL, &n, true)); + BOOST_CHECK_EQUAL(n, marshal::visit_eterm_encode_size_calc()(true)); + n = 0; + BOOST_CHECK_EQUAL(0, ei_encode_boolean(NULL, &n, false)); + BOOST_CHECK_EQUAL(n, marshal::visit_eterm_encode_size_calc()(false)); + n = 0; + BOOST_CHECK_EQUAL(0, ei_encode_double(NULL, &n, 0.0)); + BOOST_CHECK_EQUAL(n, marshal::visit_eterm_encode_size_calc()(0.0)); + } + + { + const uint8_t buf[] = {ERL_ATOM_UTF8_EXT,0,4,116,114,117,101}; + uintptr_t i = 0; eterm t((const char*)buf, i, sizeof(buf), alloc); - BOOST_REQUIRE_EQUAL(true, t.to_bool()); - BOOST_REQUIRE_EQUAL(std::string("true"), t.to_string()); + BOOST_CHECK_EQUAL(true, t.to_bool()); + BOOST_CHECK_EQUAL(std::string("true"), t.to_string()); } { - const uint8_t buf[] = {ERL_ATOM_EXT,0,5,102,97,108,115,101}; - int i = 0; + const uint8_t buf[] = {ERL_ATOM_UTF8_EXT,0,5,102,97,108,115,101}; + uintptr_t i = 0; eterm t((const char*)buf, i, sizeof(buf), alloc); - BOOST_REQUIRE_EQUAL(sizeof(buf), (size_t)i); - BOOST_REQUIRE_EQUAL(false, t.to_bool()); - BOOST_REQUIRE_EQUAL(std::string("false"), t.to_string()); + BOOST_CHECK_EQUAL(sizeof(buf), (size_t)i); + BOOST_CHECK_EQUAL(false, t.to_bool()); + BOOST_CHECK_EQUAL(std::string("false"), t.to_string()); } } @@ -107,16 +173,22 @@ BOOST_AUTO_TEST_CASE( test_binary ) { binary et("Abc", 3, alloc); } { binary et("Abc", 3); } { binary et("Abc", 3, alloc); } + { + binary et{1,2,109}; + BOOST_CHECK_EQUAL(3u, et.size()); + BOOST_CHECK_EQUAL("<<1,2,109>>", eterm(et).to_string()); + BOOST_CHECK_EQUAL("<<>>", eterm(binary({}, alloc)).to_string()); + } { const uint8_t buf[] = {ERL_BINARY_EXT,0,0,0,3,97,98,99}; - int i = 0; + uintptr_t i = 0; binary term1((const char*)buf, i, sizeof(buf), alloc); i = 0; binary term2((const char*)buf, i, sizeof(buf), alloc); - BOOST_REQUIRE_EQUAL(term1, term2); + BOOST_CHECK_EQUAL(term1, term2); eterm et(term1); - BOOST_REQUIRE_EQUAL("<<\"abc\">>", et.to_string()); + BOOST_CHECK_EQUAL("<<\"abc\">>", et.to_string()); } } @@ -132,7 +204,13 @@ BOOST_AUTO_TEST_CASE( test_list ) eterm(atom("efg")) }; eterm et = list(l); - BOOST_REQUIRE(et.initialized()); + BOOST_CHECK(et.initialized()); + } + { + list l{1, 2, 3, "abc", 2.0, atom("efg")}; + eterm et = l; + BOOST_CHECK_EQUAL(6, l.length()); + BOOST_CHECK(et.initialized()); } { eterm items[] = { @@ -142,22 +220,22 @@ BOOST_AUTO_TEST_CASE( test_list ) list l(2, alloc); l.push_back(items[0]); l.push_back(items[1]); - BOOST_REQUIRE(!l.initialized()); + BOOST_CHECK(!l.initialized()); l.close(); - BOOST_REQUIRE(l.initialized()); - BOOST_REQUIRE_EQUAL(2u, l.length()); + BOOST_CHECK(l.initialized()); + BOOST_CHECK_EQUAL(2u, l.length()); eterm et(l); - BOOST_REQUIRE_EQUAL(2u, et.to_list().length()); + BOOST_CHECK_EQUAL(2u, et.to_list().length()); } { eterm items[] = { eterm(atom("abc")), eterm(atom("efg")) }; list l(items, alloc); - BOOST_REQUIRE(l.initialized()); - BOOST_REQUIRE_EQUAL(2u, l.length()); + BOOST_CHECK(l.initialized()); + BOOST_CHECK_EQUAL(2u, l.length()); list::iterator it = l.begin(); - BOOST_REQUIRE_EQUAL("efg", (++it)->to_string()); + BOOST_CHECK_EQUAL("efg", (++it)->to_string()); eterm et(l); - BOOST_REQUIRE_EQUAL("[abc,efg]", et.to_string()); + BOOST_CHECK_EQUAL("[abc,efg]", et.to_string()); } { eterm items[] = { @@ -166,16 +244,16 @@ BOOST_AUTO_TEST_CASE( test_list ) eterm(3) }; list et(items, alloc); - BOOST_REQUIRE_EQUAL(3u, et.length()); + BOOST_CHECK_EQUAL(3u, et.length()); const list& cp1 = et.tail(0); - BOOST_REQUIRE_EQUAL(2u, cp1.length()); + BOOST_CHECK_EQUAL(2u, cp1.length()); list::const_iterator it = cp1.begin(); - BOOST_REQUIRE_EQUAL(LONG, it->type()); - BOOST_REQUIRE_EQUAL(2, (it++)->to_long()); - BOOST_REQUIRE_EQUAL(LONG, it->type()); - BOOST_REQUIRE_EQUAL(3, (it++)->to_long()); - BOOST_REQUIRE(cp1.end() == it); + BOOST_CHECK_EQUAL(LONG, it->type()); + BOOST_CHECK_EQUAL(2, (it++)->to_long()); + BOOST_CHECK_EQUAL(LONG, it->type()); + BOOST_CHECK_EQUAL(3, (it++)->to_long()); + BOOST_CHECK(cp1.end() == it); } } @@ -184,48 +262,48 @@ BOOST_AUTO_TEST_CASE( test_list3 ) allocator_t alloc; { list t = list::make(1, alloc); - BOOST_REQUIRE_EQUAL(1ul, t.length()); - BOOST_REQUIRE_EQUAL(1, t.nth(0).to_long()); + BOOST_CHECK_EQUAL(1ul, t.length()); + BOOST_CHECK_EQUAL(1l, t.nth(0).to_long()); } { const list& t = list::make(1, 2, alloc); - BOOST_REQUIRE_EQUAL(2ul, t.length()); - BOOST_REQUIRE_EQUAL(1, t.nth(0).to_long()); - BOOST_REQUIRE_EQUAL(2, t.nth(1).to_long()); + BOOST_CHECK_EQUAL(2ul, t.length()); + BOOST_CHECK_EQUAL(1, t.nth(0).to_long()); + BOOST_CHECK_EQUAL(2, t.nth(1).to_long()); } { const list& t = list::make(1,2,3, alloc); - BOOST_REQUIRE_EQUAL(3ul, t.length()); - BOOST_REQUIRE_EQUAL(1, t.nth(0).to_long()); - BOOST_REQUIRE_EQUAL(2, t.nth(1).to_long()); - BOOST_REQUIRE_EQUAL(3, t.nth(2).to_long()); + BOOST_CHECK_EQUAL(3ul, t.length()); + BOOST_CHECK_EQUAL(1, t.nth(0).to_long()); + BOOST_CHECK_EQUAL(2, t.nth(1).to_long()); + BOOST_CHECK_EQUAL(3, t.nth(2).to_long()); } { const list& t = list::make(1,2,3,4, alloc); - BOOST_REQUIRE_EQUAL(4ul, t.length()); - BOOST_REQUIRE_EQUAL(1, t.nth(0).to_long()); - BOOST_REQUIRE_EQUAL(2, t.nth(1).to_long()); - BOOST_REQUIRE_EQUAL(3, t.nth(2).to_long()); - BOOST_REQUIRE_EQUAL(4, t.nth(3).to_long()); + BOOST_CHECK_EQUAL(4ul, t.length()); + BOOST_CHECK_EQUAL(1, t.nth(0).to_long()); + BOOST_CHECK_EQUAL(2, t.nth(1).to_long()); + BOOST_CHECK_EQUAL(3, t.nth(2).to_long()); + BOOST_CHECK_EQUAL(4, t.nth(3).to_long()); } { const list& t = list::make(1,2,3,4,5, alloc); - BOOST_REQUIRE_EQUAL(5ul, t.length()); - BOOST_REQUIRE_EQUAL(1, t.nth(0).to_long()); - BOOST_REQUIRE_EQUAL(2, t.nth(1).to_long()); - BOOST_REQUIRE_EQUAL(3, t.nth(2).to_long()); - BOOST_REQUIRE_EQUAL(4, t.nth(3).to_long()); - BOOST_REQUIRE_EQUAL(5, t.nth(4).to_long()); + BOOST_CHECK_EQUAL(5ul, t.length()); + BOOST_CHECK_EQUAL(1, t.nth(0).to_long()); + BOOST_CHECK_EQUAL(2, t.nth(1).to_long()); + BOOST_CHECK_EQUAL(3, t.nth(2).to_long()); + BOOST_CHECK_EQUAL(4, t.nth(3).to_long()); + BOOST_CHECK_EQUAL(5, t.nth(4).to_long()); } { const list& t = list::make(1,2,3,4,5,6, alloc); - BOOST_REQUIRE_EQUAL(6ul, t.length()); - BOOST_REQUIRE_EQUAL(1, t.nth(0).to_long()); - BOOST_REQUIRE_EQUAL(2, t.nth(1).to_long()); - BOOST_REQUIRE_EQUAL(3, t.nth(2).to_long()); - BOOST_REQUIRE_EQUAL(4, t.nth(3).to_long()); - BOOST_REQUIRE_EQUAL(5, t.nth(4).to_long()); - BOOST_REQUIRE_EQUAL(6, t.nth(5).to_long()); + BOOST_CHECK_EQUAL(6ul, t.length()); + BOOST_CHECK_EQUAL(1, t.nth(0).to_long()); + BOOST_CHECK_EQUAL(2, t.nth(1).to_long()); + BOOST_CHECK_EQUAL(3, t.nth(2).to_long()); + BOOST_CHECK_EQUAL(4, t.nth(3).to_long()); + BOOST_CHECK_EQUAL(5, t.nth(4).to_long()); + BOOST_CHECK_EQUAL(6, t.nth(5).to_long()); } } @@ -235,6 +313,25 @@ BOOST_AUTO_TEST_CASE( test_list4 ) for (int i=0; i<2; ++i) l.push_back(eterm(atom("abc"))); l.close(); + BOOST_CHECK_EQUAL(2u, l.length()); + + { + list l1{tuple{am_ok, 10}, tuple{am_error, "abc"}}; + + atom opt; + eterm val; + + for (auto& item : l1) { + BOOST_CHECK(item.to_pair(opt, val)); + if (opt == am_ok) + BOOST_CHECK_EQUAL(10, val.to_long()); + else if (opt == am_error) + BOOST_CHECK_EQUAL("abc", val.to_str()); + else + BOOST_CHECK(false); + } + + } } BOOST_AUTO_TEST_CASE( test_double ) @@ -242,38 +339,38 @@ BOOST_AUTO_TEST_CASE( test_double ) allocator_t alloc; { eterm et1(10.0); - BOOST_REQUIRE_EQUAL(DOUBLE, et1.type()); - BOOST_REQUIRE(et1.initialized()); + BOOST_CHECK_EQUAL(DOUBLE, et1.type()); + BOOST_CHECK(et1.initialized()); } { const uint8_t buf[] = {ERL_FLOAT_EXT,49,46,48,48,48,48,48,48,48, 48,48,48,48,48,48,48,48,48,48,48,48,48,101, 43,48,48,0,0,0,0,0}; - int i = 0; + uintptr_t i = 0; eterm term((const char*)buf, i, sizeof(buf), alloc); - BOOST_REQUIRE_EQUAL(32, i); - BOOST_REQUIRE_EQUAL(1.0, term.to_double()); + BOOST_CHECK_EQUAL(32, i); + BOOST_CHECK_EQUAL(1.0, term.to_double()); } { const uint8_t buf[] = {NEW_FLOAT_EXT,63,240,0,0,0,0,0,0}; - int i = 0; + uintptr_t i = 0; eterm term((const char*)buf, i, sizeof(buf), alloc); - BOOST_REQUIRE_EQUAL(9, i); - BOOST_REQUIRE_EQUAL(1.0, term.to_double()); - BOOST_REQUIRE_EQUAL("1.0", term.to_string()); + BOOST_CHECK_EQUAL(9, i); + BOOST_CHECK_EQUAL(1.0, term.to_double()); + BOOST_CHECK_EQUAL("1.0", term.to_string()); } { eterm term(90.0); - BOOST_REQUIRE_EQUAL("90.0", term.to_string()); + BOOST_CHECK_EQUAL("90.0", term.to_string()); } { eterm term(900.0); - BOOST_REQUIRE_EQUAL("900.0", term.to_string()); + BOOST_CHECK_EQUAL("900.0", term.to_string()); } { eterm term(90.010000); - BOOST_REQUIRE_EQUAL("90.01", term.to_string()); + BOOST_CHECK_EQUAL("90.01", term.to_string()); } } @@ -282,28 +379,28 @@ BOOST_AUTO_TEST_CASE( test_long ) allocator_t alloc; { eterm et(100l * 1024 * 1024 * 1024); - BOOST_REQUIRE_EQUAL(LONG, et.type()); - BOOST_REQUIRE_EQUAL(100l * 1024 * 1024 * 1024, et.to_long()); + BOOST_CHECK_EQUAL(LONG, et.type()); + BOOST_CHECK_EQUAL(100l * 1024 * 1024 * 1024, et.to_long()); } { eterm et(1); - BOOST_REQUIRE(et.initialized()); + BOOST_CHECK(et.initialized()); } { const uint8_t buf[] = {ERL_INTEGER_EXT,7,91,205,21}; - int i = 0; + uintptr_t i = 0; eterm term((const char*)buf, i, sizeof(buf), alloc); - BOOST_REQUIRE_EQUAL(5, i); - BOOST_REQUIRE_EQUAL (123456789, term.to_long()); - BOOST_REQUIRE_EQUAL("123456789", term.to_string()); + BOOST_CHECK_EQUAL(5, i); + BOOST_CHECK_EQUAL (123456789, term.to_long()); + BOOST_CHECK_EQUAL("123456789", term.to_string()); } { const uint8_t buf[] = {ERL_SMALL_BIG_EXT,4,1,210,2,150,73}; - int i = 0; + uintptr_t i = 0; eterm term((const char*)buf, i, sizeof(buf), alloc); - BOOST_REQUIRE_EQUAL(7, i); - BOOST_REQUIRE_EQUAL (-1234567890, term.to_long()); - BOOST_REQUIRE_EQUAL("-1234567890", term.to_string()); + BOOST_CHECK_EQUAL(7, i); + BOOST_CHECK_EQUAL (-1234567890, term.to_long()); + BOOST_CHECK_EQUAL("-1234567890", term.to_string()); } } @@ -312,27 +409,27 @@ BOOST_AUTO_TEST_CASE( test_string ) allocator_t alloc; { eterm et("Abc", alloc); - BOOST_REQUIRE(et.initialized()); - BOOST_REQUIRE_EQUAL(STRING, et.type()); + BOOST_CHECK(et.initialized()); + BOOST_CHECK_EQUAL(STRING, et.type()); } { string s("a", alloc); std::string test("abcd"); s = test; - BOOST_REQUIRE_EQUAL(s, "abcd"); + BOOST_CHECK_EQUAL(s, "abcd"); } { const uint8_t buf[] = {ERL_STRING_EXT,0,3,97,98,99}; - int i = 0; + uintptr_t i = 0; eterm term((const char*)buf, i, sizeof(buf), alloc); - BOOST_REQUIRE_EQUAL(6, i); - BOOST_REQUIRE_EQUAL("abc", term.to_str()); - BOOST_REQUIRE_EQUAL(term.to_str(), "abc"); - BOOST_REQUIRE_EQUAL(std::string("abc"), term.to_str()); - BOOST_REQUIRE_EQUAL(term.to_str(), std::string("abc")); - BOOST_REQUIRE_EQUAL(std::string("\"abc\""), term.to_string()); + BOOST_CHECK_EQUAL(6, i); + BOOST_CHECK_EQUAL("abc", term.to_str()); + BOOST_CHECK_EQUAL(term.to_str(), "abc"); + BOOST_CHECK_EQUAL(std::string("abc"), term.to_str()); + BOOST_CHECK_EQUAL(term.to_str(), std::string("abc")); + BOOST_CHECK_EQUAL(std::string("\"abc\""), term.to_string()); } } @@ -341,28 +438,68 @@ BOOST_AUTO_TEST_CASE( test_pid ) allocator_t alloc; { epid et("abc@fc12", 1, 2, 3, alloc); - BOOST_REQUIRE_EQUAL(atom("abc@fc12"), et.node()); - BOOST_REQUIRE_EQUAL(1, et.id()); - BOOST_REQUIRE_EQUAL(2, et.serial()); - BOOST_REQUIRE_EQUAL(3, et.creation()); + BOOST_CHECK_EQUAL(atom("abc@fc12"), et.node()); + BOOST_CHECK_EQUAL(1, et.id()); + BOOST_CHECK_EQUAL(2, et.serial()); + BOOST_CHECK_EQUAL(3, et.creation()); + + et = epid("abc@fc12", 1, 2, 4, alloc); + BOOST_CHECK_EQUAL(4, et.creation()); + eterm t(et); - BOOST_REQUIRE(t.initialized()); - BOOST_REQUIRE_EQUAL(PID, t.type()); - BOOST_REQUIRE_EQUAL("#Pid", t.to_string()); + BOOST_CHECK(t.initialized()); + BOOST_CHECK_EQUAL(PID, t.type()); + BOOST_CHECK_EQUAL("#Pid", t.to_string()); + config::display_creation(false); + BOOST_CHECK_EQUAL("#Pid", t.to_string()); + config::display_creation(true); + + BOOST_CHECK_EQUAL("#Pid", eterm(epid("abc@fc12", 1, 2, 0, alloc)).to_string()); } { epid p1("a@fc12", 1, 2, 3, alloc); epid p2("a@fc12", 1, 2, 3, alloc); - BOOST_REQUIRE_EQUAL(p1, p2); + BOOST_CHECK_EQUAL(p1, p2); epid p3("a@fc", 1, 2, 3, alloc); - BOOST_REQUIRE_NE(p1, p3); + BOOST_CHECK_NE(p1, p3); epid p4("a@fc12", 4, 2, 3, alloc); - BOOST_REQUIRE_NE(p1, p4); + BOOST_CHECK_NE(p1, p4); epid p5("a@fc12", 1, 4, 3, alloc); - BOOST_REQUIRE_NE(p1, p5); + BOOST_CHECK_NE(p1, p5); epid p6("a@fc12", 1, 2, 4, alloc); - BOOST_REQUIRE_NE(p1, p6); + BOOST_CHECK_NE(p1, p6); + } +} + +BOOST_AUTO_TEST_CASE( test_map ) +{ + allocator_t alloc; + { + map m00, m01; + BOOST_CHECK_EQUAL(m00, m01); + + map m{{1, 2.00}, {"abc", 10}}; + BOOST_CHECK_EQUAL(2ul, m.size()); + BOOST_CHECK_EQUAL(2.00, m[1].to_double()); + BOOST_CHECK_EQUAL(10, m["abc"].to_long()); + + map m1{{1, 2.00}, {"abc", 10}}; + BOOST_CHECK_EQUAL(m, m1); + + map m2{{1, 3.00}, {"abc", 10}}; + BOOST_CHECK_LT(m, m2); + } + { + // #{1=>2, a => 3} + const uint8_t buf[] = {ERL_MAP_EXT,0,0,0,2,97,1,97,2,100,0,1,97,97,3}; + uintptr_t i = 0; + eterm term((const char*)buf, i, sizeof(buf), alloc); + BOOST_CHECK_EQUAL(15, i); + BOOST_CHECK(term.is_map()); + BOOST_CHECK_EQUAL(2, term.to_map().size()); + BOOST_CHECK_EQUAL(2, term.to_map()[1].to_long()); + BOOST_CHECK_EQUAL(3, term.to_map()[atom("a")].to_long()); } } @@ -378,6 +515,7 @@ BOOST_AUTO_TEST_CASE( test_less_then ) std::set(); std::set(); std::set(); + std::set(); } { @@ -390,7 +528,7 @@ BOOST_AUTO_TEST_CASE( test_less_then ) ss.insert(et1); ss.insert(et2); ss.insert(et1); - BOOST_REQUIRE_EQUAL(2ul, ss.size()); + BOOST_CHECK_EQUAL(2ul, ss.size()); } } @@ -399,25 +537,33 @@ BOOST_AUTO_TEST_CASE( test_port ) allocator_t alloc; { port et("abc@fc12", 1, 2, alloc); - BOOST_REQUIRE_EQUAL(atom("abc@fc12"), et.node()); - BOOST_REQUIRE_EQUAL(1, et.id()); - BOOST_REQUIRE_EQUAL(2, et.creation()); + BOOST_CHECK_EQUAL(atom("abc@fc12"), et.node()); + BOOST_CHECK_EQUAL(1, et.id()); + BOOST_CHECK_EQUAL(2, et.creation()); eterm t(et); - BOOST_REQUIRE(t.initialized()); - BOOST_REQUIRE_EQUAL(PORT, t.type()); - BOOST_REQUIRE_EQUAL("#Port", t.to_string()); + BOOST_CHECK(t.initialized()); + BOOST_CHECK_EQUAL(PORT, t.type()); + BOOST_CHECK_EQUAL("#Port", t.to_string()); + config::display_creation(false); + BOOST_CHECK_EQUAL("#Port", t.to_string()); + config::display_creation(true); + BOOST_CHECK_EQUAL("#Port", eterm(port("abc@fc12",1,0)).to_string()); + port et1("abc@fc12", 1, 2, alloc); + port et2("abc@fc12", 1, 0, alloc); + BOOST_CHECK_EQUAL(et1, et); + BOOST_CHECK_NE(et1, et2); } { port p1("a@fc12", 1, 2, alloc); port p2("a@fc12", 1, 2); - BOOST_REQUIRE_EQUAL(p1, p2); + BOOST_CHECK_EQUAL(p1, p2); port p3("a@fc", 1, 2, alloc); - BOOST_REQUIRE_NE(p1, p3); + BOOST_CHECK_NE(p1, p3); port p4("a@fc12", 4, 2, alloc); - BOOST_REQUIRE_NE(p1, p4); + BOOST_CHECK_NE(p1, p4); port p5("a@fc12", 1, 4, alloc); - BOOST_REQUIRE_NE(p1, p5); + BOOST_CHECK_NE(p1, p5); } } @@ -425,40 +571,54 @@ BOOST_AUTO_TEST_CASE( test_ref ) { allocator_t alloc; { - uint32_t ids[] = {1,2,3}; - ref et("abc@fc12", ids, 4, alloc); - BOOST_REQUIRE_EQUAL(atom("abc@fc12"), et.node()); - BOOST_REQUIRE_EQUAL(1u, et.id(0)); - BOOST_REQUIRE_EQUAL(2u, et.id(1)); - BOOST_REQUIRE_EQUAL(3u, et.id(2)); - BOOST_REQUIRE_EQUAL(4, et.creation()); + uint32_t ids[] = {5,6,7}; + ref et("abc@fc12", ids, 3, alloc); + BOOST_CHECK_EQUAL(atom("abc@fc12"), et.node()); + BOOST_CHECK_EQUAL(5u, et.id(0)); + BOOST_CHECK_EQUAL(6u, et.id(1)); + BOOST_CHECK_EQUAL(7u, et.id(2)); + BOOST_CHECK_EQUAL(3, et.creation()); + + ref et2("abc@fc12", ids, 3, alloc); + BOOST_CHECK_EQUAL(et, et2); + + et = ref("abc@fc12", ids, 4, alloc); + BOOST_CHECK_EQUAL(4, et.creation()); + eterm t(et); - BOOST_REQUIRE(t.initialized()); - BOOST_REQUIRE_EQUAL(REF, t.type()); - BOOST_REQUIRE_EQUAL("#Ref", t.to_string()); + BOOST_CHECK(t.initialized()); + BOOST_CHECK_EQUAL(REF, t.type()); + BOOST_CHECK_EQUAL("#Ref", t.to_string()); + config::display_creation(false); + BOOST_CHECK_EQUAL("#Ref", t.to_string()); + config::display_creation(true); + ref et1("abc@fc12", ids, 0, alloc); + BOOST_CHECK_EQUAL("#Ref", eterm(et1).to_string()); + BOOST_CHECK_NE(et, et1); + BOOST_CHECK_NE(et1, et2); } { uint32_t ids[] = {1,2,3}; ref p1("abc@fc12", ids, 4, alloc); ref p2("abc@fc12", ids, 4); - BOOST_REQUIRE_EQUAL(p1, p2); + BOOST_CHECK_EQUAL(p1, p2); ids[0] = 4; ref p3("abc@fc12", ids, 4); - BOOST_REQUIRE_NE(p1, p3); + BOOST_CHECK_NE(p1, p3); ids[0] = 1; ids[1] = 4; ref p4("abc@fc12", ids, 4); - BOOST_REQUIRE_NE(p1, p4); + BOOST_CHECK_NE(p1, p4); ids[1] = 2; ids[2] = 4; ref p5("abc@fc12", ids, 4); - BOOST_REQUIRE_NE(p1, p5); + BOOST_CHECK_NE(p1, p5); ids[2] = 3; ref p6("abc@fc12", ids, 4); - BOOST_REQUIRE_EQUAL(p1, p6); + BOOST_CHECK_EQUAL(p1, p6); ref p7("abc@fc12", ids, 5); - BOOST_REQUIRE_NE(p1, p7); + BOOST_CHECK_NE(p1, p7); } } @@ -467,7 +627,7 @@ BOOST_AUTO_TEST_CASE( test_tuple ) allocator_t alloc; { tuple et2(10, alloc); - BOOST_REQUIRE(!et2.initialized()); + BOOST_CHECK(!et2.initialized()); } { eterm l[] = { @@ -475,7 +635,13 @@ BOOST_AUTO_TEST_CASE( test_tuple ) eterm(atom("efg")) }; eterm et = tuple(l); - BOOST_REQUIRE(et.initialized()); + BOOST_CHECK(et.initialized()); + } + + { + tuple t{1, 2, 3, "abc", 2.0, atom("efg")}; + eterm et = t; + BOOST_CHECK(et.initialized()); } eterm l[] = { @@ -492,9 +658,9 @@ BOOST_AUTO_TEST_CASE( test_tuple ) // Note that now the tuple owns previously dangling eterm pointers // in the list[] array. - BOOST_REQUIRE(et.initialized()); - BOOST_REQUIRE_EQUAL(4u, et.size()); - BOOST_REQUIRE_EQUAL("efg", et[1].to_string()); + BOOST_CHECK(et.initialized()); + BOOST_CHECK_EQUAL(4u, et.size()); + BOOST_CHECK_EQUAL("efg", et[1].to_string()); } BOOST_AUTO_TEST_CASE( test_tuple2 ) @@ -505,11 +671,11 @@ BOOST_AUTO_TEST_CASE( test_tuple2 ) tuple et(2, alloc); et.push_back(items[0]); et.push_back(items[1]); - BOOST_REQUIRE(et.initialized()); - BOOST_REQUIRE_EQUAL(2u, et.size()); - BOOST_REQUIRE_EQUAL("efg", et[1].to_string()); + BOOST_CHECK(et.initialized()); + BOOST_CHECK_EQUAL(2u, et.size()); + BOOST_CHECK_EQUAL("efg", et[1].to_string()); eterm term(et); - BOOST_REQUIRE_EQUAL("{'Abc',efg}", term.to_string()); + BOOST_CHECK_EQUAL("{'Abc',efg}", term.to_string()); } } @@ -518,48 +684,48 @@ BOOST_AUTO_TEST_CASE( test_tuple3 ) allocator_t alloc; { tuple t = tuple::make(1, alloc); - BOOST_REQUIRE_EQUAL(1ul, t.size()); - BOOST_REQUIRE_EQUAL(1, t[0].to_long()); + BOOST_CHECK_EQUAL(1ul, t.size()); + BOOST_CHECK_EQUAL(1, t[0].to_long()); } { const tuple& t = tuple::make(1, 2, alloc); - BOOST_REQUIRE_EQUAL(2ul, t.size()); - BOOST_REQUIRE_EQUAL(1, t[0].to_long()); - BOOST_REQUIRE_EQUAL(2, t[1].to_long()); + BOOST_CHECK_EQUAL(2ul, t.size()); + BOOST_CHECK_EQUAL(1, t[0].to_long()); + BOOST_CHECK_EQUAL(2, t[1].to_long()); } { const tuple& t = tuple::make(1,2,3, alloc); - BOOST_REQUIRE_EQUAL(3ul, t.size()); - BOOST_REQUIRE_EQUAL(1, t[0].to_long()); - BOOST_REQUIRE_EQUAL(2, t[1].to_long()); - BOOST_REQUIRE_EQUAL(3, t[2].to_long()); + BOOST_CHECK_EQUAL(3ul, t.size()); + BOOST_CHECK_EQUAL(1, t[0].to_long()); + BOOST_CHECK_EQUAL(2, t[1].to_long()); + BOOST_CHECK_EQUAL(3, t[2].to_long()); } { const tuple& t = tuple::make(1,2,3,4, alloc); - BOOST_REQUIRE_EQUAL(4ul, t.size()); - BOOST_REQUIRE_EQUAL(1, t[0].to_long()); - BOOST_REQUIRE_EQUAL(2, t[1].to_long()); - BOOST_REQUIRE_EQUAL(3, t[2].to_long()); - BOOST_REQUIRE_EQUAL(4, t[3].to_long()); + BOOST_CHECK_EQUAL(4ul, t.size()); + BOOST_CHECK_EQUAL(1, t[0].to_long()); + BOOST_CHECK_EQUAL(2, t[1].to_long()); + BOOST_CHECK_EQUAL(3, t[2].to_long()); + BOOST_CHECK_EQUAL(4, t[3].to_long()); } { const tuple& t = tuple::make(1,2,3,4,5, alloc); - BOOST_REQUIRE_EQUAL(5ul, t.size()); - BOOST_REQUIRE_EQUAL(1, t[0].to_long()); - BOOST_REQUIRE_EQUAL(2, t[1].to_long()); - BOOST_REQUIRE_EQUAL(3, t[2].to_long()); - BOOST_REQUIRE_EQUAL(4, t[3].to_long()); - BOOST_REQUIRE_EQUAL(5, t[4].to_long()); + BOOST_CHECK_EQUAL(5ul, t.size()); + BOOST_CHECK_EQUAL(1, t[0].to_long()); + BOOST_CHECK_EQUAL(2, t[1].to_long()); + BOOST_CHECK_EQUAL(3, t[2].to_long()); + BOOST_CHECK_EQUAL(4, t[3].to_long()); + BOOST_CHECK_EQUAL(5, t[4].to_long()); } { const tuple& t = tuple::make(1,2,3,4,5,6, alloc); - BOOST_REQUIRE_EQUAL(6ul, t.size()); - BOOST_REQUIRE_EQUAL(1, t[0].to_long()); - BOOST_REQUIRE_EQUAL(2, t[1].to_long()); - BOOST_REQUIRE_EQUAL(3, t[2].to_long()); - BOOST_REQUIRE_EQUAL(4, t[3].to_long()); - BOOST_REQUIRE_EQUAL(5, t[4].to_long()); - BOOST_REQUIRE_EQUAL(6, t[5].to_long()); + BOOST_CHECK_EQUAL(6ul, t.size()); + BOOST_CHECK_EQUAL(1, t[0].to_long()); + BOOST_CHECK_EQUAL(2, t[1].to_long()); + BOOST_CHECK_EQUAL(3, t[2].to_long()); + BOOST_CHECK_EQUAL(4, t[3].to_long()); + BOOST_CHECK_EQUAL(5, t[4].to_long()); + BOOST_CHECK_EQUAL(6, t[5].to_long()); } } @@ -577,14 +743,14 @@ BOOST_AUTO_TEST_CASE( test_trace ) eterm et4(tr4); trace tr5(1, 2, 3, epid("a@host",5,1,0,alloc), 6, alloc); eterm et5(tr5); - BOOST_REQUIRE(et1.initialized()); - BOOST_REQUIRE_EQUAL(TRACE, et1.type()); - BOOST_REQUIRE(et1 == et1); - BOOST_REQUIRE(et1 != et2); - BOOST_REQUIRE(et1 != et3); - BOOST_REQUIRE(et1 != et4); - BOOST_REQUIRE(et1 != et5); - BOOST_REQUIRE_EQUAL("{1,2,3,#Pid,4}", et1.to_string()); + BOOST_CHECK(et1.initialized()); + BOOST_CHECK_EQUAL(TRACE, et1.type()); + BOOST_CHECK(et1 == et1); + BOOST_CHECK(et1 != et2); + BOOST_CHECK(et1 != et3); + BOOST_CHECK(et1 != et4); + BOOST_CHECK(et1 != et5); + BOOST_CHECK_EQUAL("{1,2,3,#Pid,4}", et1.to_string()); } } @@ -592,28 +758,42 @@ BOOST_AUTO_TEST_CASE( test_varbind ) { allocator_t alloc; - { - string a("Atom", alloc); - eterm ea(a); - - eterm list[] = { - ea, - eterm(123) - }; - - { - varbind binding1; - binding1.bind(a, list[0]); - binding1.bind("Long", list[1]); - varbind binding2; - binding2.bind("Atom", eterm(atom("test"))); - binding2.bind(string("Other", alloc), list[0]); - - binding1.merge(binding2); - - BOOST_REQUIRE_EQUAL(3ul, binding1.count()); - } - } + varbind binding1; + EIXX_DECL_ATOM(Name); + binding1.bind(am_Name, 20.0); + binding1.bind(atom("Long"), 123); + varbind binding2; + binding2.bind(am_Name, atom("test")); + binding2.bind(atom("Other"), "vasya"); + + binding1.merge(binding2); + + BOOST_CHECK_EQUAL(3ul, binding1.count()); + BOOST_CHECK(binding1[am_Name]); + BOOST_CHECK_EQUAL(eterm(20.0), binding1.get(am_Name)); + +#if __cplusplus >= 201103L + EIXX_DECL_ATOM_VAR(am_a, "A"); + EIXX_DECL_ATOM_VAR(am_b, "B"); + EIXX_DECL_ATOM_VAR(am_c, "C"); + + varbind binding3{ {am_a, 10}, {am_b, 200.0}, {"C", "abc"} }; + BOOST_CHECK_EQUAL(3u, binding3.count()); + + BOOST_CHECK_EQUAL(10, binding3[am_a]->to_long()); + BOOST_CHECK_EQUAL(200.0, binding3[am_b]->to_double()); + BOOST_CHECK_EQUAL("abc", binding3[am_c]->to_str()); + + eterm term = eterm::format("{ok, A::int(), B::float(), C::string()}"); + eterm got0 = eterm::format("{ok, 10, 200.0, \"abc\"}"); + eterm got1 = term.apply({{am_a, 10}, {am_b, 200.0}, {"C", "abc"}}); + eterm got2 = term.apply({{am_a, 10}, {am_b, 200.0}, {am_c, "abc"}}); + eterm got3 = term.apply(binding3); + + BOOST_CHECK(got0 == got1); + BOOST_CHECK(got0 == got2); + BOOST_CHECK(got0 == got3); +#endif } eterm f() { @@ -625,32 +805,32 @@ BOOST_AUTO_TEST_CASE( test_assign ) { { eterm a = f(); - BOOST_REQUIRE_EQUAL(STRING, a.type()); - BOOST_REQUIRE_EQUAL("abcd", a.to_str()); + BOOST_CHECK_EQUAL(STRING, a.type()); + BOOST_CHECK_EQUAL("abcd", a.to_str()); } eterm a; { a.set( f() ); - BOOST_REQUIRE_EQUAL(STRING, a.type()); - BOOST_REQUIRE_EQUAL("abcd", a.to_str()); + BOOST_CHECK_EQUAL(STRING, a.type()); + BOOST_CHECK_EQUAL("abcd", a.to_str()); } { a = f(); - BOOST_REQUIRE_EQUAL(STRING, a.type()); - BOOST_REQUIRE_EQUAL("abcd", a.to_str()); + BOOST_CHECK_EQUAL(STRING, a.type()); + BOOST_CHECK_EQUAL("abcd", a.to_str()); } { eterm b("abcd"); eterm c = b; - BOOST_REQUIRE_EQUAL(STRING, c.type()); - BOOST_REQUIRE_EQUAL("abcd", c.to_str()); + BOOST_CHECK_EQUAL(STRING, c.type()); + BOOST_CHECK_EQUAL("abcd", c.to_str()); c = "ddd"; - BOOST_REQUIRE_EQUAL(STRING, c.type()); - BOOST_REQUIRE_EQUAL("ddd", c.to_str()); + BOOST_CHECK_EQUAL(STRING, c.type()); + BOOST_CHECK_EQUAL("ddd", c.to_str()); c.set( f() ); - BOOST_REQUIRE_EQUAL(STRING, c.type()); - BOOST_REQUIRE_EQUAL("abcd", c.to_str()); + BOOST_CHECK_EQUAL(STRING, c.type()); + BOOST_CHECK_EQUAL("abcd", c.to_str()); } } @@ -678,29 +858,29 @@ BOOST_AUTO_TEST_CASE( test_cast ) const list& l = ll[0].to_list(); const tuple& t = ll[1].to_tuple(); - BOOST_REQUIRE_EQUAL(true, t[0].to_bool()); - BOOST_REQUIRE_EQUAL(true, l.begin()->to_bool()); + BOOST_CHECK_EQUAL(true, t[0].to_bool()); + BOOST_CHECK_EQUAL(true, l.begin()->to_bool()); tuple et(ll, alloc); - BOOST_REQUIRE_EQUAL(sizeof(ll) / sizeof(eterm), et.size()); + BOOST_CHECK_EQUAL(sizeof(ll) / sizeof(eterm), et.size()); - BOOST_REQUIRE_EQUAL(1u, ll[0].to_list().length()); - BOOST_REQUIRE_EQUAL(1u, ll[1].to_tuple().size()); - BOOST_REQUIRE_EQUAL("test", ll[2].to_atom()); - BOOST_REQUIRE_EQUAL(123, ll[3].to_long()); - BOOST_REQUIRE_EQUAL(1.0, ll[4].to_double()); - BOOST_REQUIRE_EQUAL(true, ll[5].to_bool()); - BOOST_REQUIRE_EQUAL("ABC", ll[6].to_str()); + BOOST_CHECK_EQUAL(1u, ll[0].to_list().length()); + BOOST_CHECK_EQUAL(1u, ll[1].to_tuple().size()); + BOOST_CHECK_EQUAL("test", ll[2].to_atom()); + BOOST_CHECK_EQUAL(123, ll[3].to_long()); + BOOST_CHECK_EQUAL(1.0, ll[4].to_double()); + BOOST_CHECK_EQUAL(true, ll[5].to_bool()); + BOOST_CHECK_EQUAL("ABC", ll[6].to_str()); } } BOOST_AUTO_TEST_CASE( test_cast2 ) { allocator_t alloc; - { eterm t( eterm::cast(1) ); BOOST_REQUIRE_EQUAL(LONG, t.type()); } - { eterm t( eterm::cast(1.0) ); BOOST_REQUIRE_EQUAL(DOUBLE, t.type()); } - { eterm t( eterm::cast(true)); BOOST_REQUIRE_EQUAL(BOOL, t.type()); } - { eterm t( eterm::cast("ab")); BOOST_REQUIRE_EQUAL(STRING, t.type()); } + { eterm t( eterm::cast(1) ); BOOST_CHECK_EQUAL(LONG, t.type()); } + { eterm t( eterm::cast(1.0) ); BOOST_CHECK_EQUAL(DOUBLE, t.type()); } + { eterm t( eterm::cast(true)); BOOST_CHECK_EQUAL(BOOL, t.type()); } + { eterm t( eterm::cast("ab")); BOOST_CHECK_EQUAL(STRING, t.type()); } } diff --git a/test/test_eterm_encode.cpp b/test/test_eterm_encode.cpp index 57168d1..0d79d14 100644 --- a/test/test_eterm_encode.cpp +++ b/test/test_eterm_encode.cpp @@ -1,23 +1,19 @@ /* ***** BEGIN LICENSE BLOCK ***** -This file is part of the EPI (Erlang Plus Interface) Library. +Copyright 2010 Serge Aleynikov -Copyright (C) 2010 Serge Aleynikov +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. + http://www.apache.org/licenses/LICENSE-2.0 -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. ***** END LICENSE BLOCK ***** */ @@ -25,6 +21,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include #include "test_alloc.hpp" #include +#include using namespace eixx; @@ -33,28 +30,37 @@ BOOST_AUTO_TEST_CASE( test_encode_string ) eterm t("abc"); string s(t.encode(0)); const uint8_t expect[] = {131,107,0,3,97,98,99}; - BOOST_REQUIRE(s.equal(expect)); - int idx = 1; // skipping the magic byte + BOOST_CHECK(s.equal(expect)); + uintptr_t idx = 1; // skipping the magic byte string t1((const char*)expect, idx, sizeof(expect)); - BOOST_REQUIRE_EQUAL(3ul, t1.size()); + BOOST_CHECK_EQUAL(3ul, t1.size()); eterm et(t1); std::string str( et.to_string() ); if (!(et == t)) - BOOST_REQUIRE_EQUAL(et, t); + BOOST_CHECK_EQUAL(et, t); if (str != "\"abc\"") - BOOST_REQUIRE_EQUAL("\"abc\"", str); + BOOST_CHECK_EQUAL("\"abc\"", str); } BOOST_AUTO_TEST_CASE( test_encode_atom ) { eterm a(atom("abc")); string s(a.encode(0)); - const uint8_t expect[] = {131,100,0,3,97,98,99}; - BOOST_REQUIRE(s.equal(expect)); - int idx = 1; // skipping the magic byte + const uint8_t expect[] = {131,119,0,3,97,98,99}; + BOOST_CHECK(s.equal(expect)); + uintptr_t idx = 1; // skipping the magic byte atom t1((const char*)expect, idx, sizeof(expect)); - BOOST_REQUIRE_EQUAL(3ul, t1.size()); - BOOST_REQUIRE_EQUAL("abc", eterm(t1).to_string()); + BOOST_CHECK_EQUAL(3ul, t1.size()); + BOOST_CHECK_EQUAL("abc", eterm(t1).to_string()); + + const uint8_t expect2[] = {131,119,2,209,132}; + eterm a2(atom("abc")); + string s2(a2.encode(0)); + BOOST_CHECK(s2.equal(expect2)); + idx = 1; // skipping the magic byte + atom t2((const char*)expect2, idx, sizeof(expect2)); + BOOST_CHECK_EQUAL(2ul, t2.size()); + BOOST_CHECK_EQUAL("ф", eterm(t2).to_string()); } BOOST_AUTO_TEST_CASE( test_encode_binary ) @@ -63,7 +69,7 @@ BOOST_AUTO_TEST_CASE( test_encode_binary ) eterm t(binary(data, sizeof(data))); string s(t.encode(0)); const uint8_t expect[] = {131,109,0,0,0,13,1,2,3,4,5,6,7,8,9,10,11,12,13}; - BOOST_REQUIRE(s.equal(expect)); + BOOST_CHECK(s.equal(expect)); } BOOST_AUTO_TEST_CASE( test_encode_double ) @@ -72,24 +78,24 @@ BOOST_AUTO_TEST_CASE( test_encode_double ) eterm t(d); string s(t.encode(0)); const uint8_t expect[] = {131,70,64,200,28,214,230,49,248,161}; - BOOST_REQUIRE(s.equal(expect)); - int idx = 1; // skipping the magic byte + BOOST_CHECK(s.equal(expect)); + uintptr_t idx = 1; // skipping the magic byte eterm t1((const char*)expect, idx, sizeof(expect)); - BOOST_REQUIRE_EQUAL(d, t1.to_double()); + BOOST_CHECK_EQUAL(d, t1.to_double()); } BOOST_AUTO_TEST_CASE( test_encode_emptylist ) { list l(0); - BOOST_REQUIRE(l.initialized()); + BOOST_CHECK(l.initialized()); eterm t(l); string s(t.encode(0)); const uint8_t expect[] = {131,106}; - BOOST_REQUIRE(s.equal(expect)); - int idx = 1; // skipping the magic byte + BOOST_CHECK(s.equal(expect)); + uintptr_t idx = 1; // skipping the magic byte eterm t1((const char*)expect, idx, sizeof(expect)); - BOOST_REQUIRE_EQUAL(LIST, t1.type()); - BOOST_REQUIRE_EQUAL(0ul, t1.to_list().length()); + BOOST_CHECK_EQUAL(LIST, t1.type()); + BOOST_CHECK_EQUAL(0ul, t1.to_list().length()); } BOOST_AUTO_TEST_CASE( test_encode_list ) @@ -103,14 +109,14 @@ BOOST_AUTO_TEST_CASE( test_encode_list ) list l(ll); eterm t(l); string s(t.encode(0)); - const uint8_t expect[] = {131,108,0,0,0,4,100,0,3,97,98,99,107,0,2,101,102, - 97,1,107,0,2,103,104,106}; - BOOST_REQUIRE(s.equal(expect)); - int idx = 1; // skipping the magic byte + const uint8_t expect[] = {131,108,0,0,0,4,ERL_ATOM_UTF8_EXT,0,3,97,98,99, + 107,0,2,101,102,97,1,107,0,2,103,104,106}; + BOOST_CHECK(s.equal(expect)); + uintptr_t idx = 1; // skipping the magic byte list t1((const char*)expect, idx, sizeof(expect)); - BOOST_REQUIRE_EQUAL(4ul, t1.length()); + BOOST_CHECK_EQUAL(4ul, t1.length()); std::string str(eterm(t1).to_string()); - BOOST_REQUIRE_EQUAL("[abc,\"ef\",1,\"gh\"]", str); + BOOST_CHECK_EQUAL("[abc,\"ef\",1,\"gh\"]", str); } BOOST_AUTO_TEST_CASE( test_encode_long ) @@ -120,30 +126,38 @@ BOOST_AUTO_TEST_CASE( test_encode_long ) eterm t(d); string s(t.encode(0)); const uint8_t expect[] = {131,97,123}; - BOOST_REQUIRE(s.equal(expect)); - int idx = 1; // skipping the magic byte + BOOST_CHECK(s.equal(expect)); + uintptr_t idx = 1; // skipping the magic byte eterm t1((const char*)expect, idx, sizeof(expect)); - BOOST_REQUIRE_EQUAL(d, t1.to_long()); + BOOST_CHECK_EQUAL(d, t1.to_long()); } { long d = 12345; eterm t(d); string s(t.encode(0)); const uint8_t expect[] = {131,98,0,0,48,57}; - BOOST_REQUIRE(s.equal(expect)); - int idx = 1; // skipping the magic byte + BOOST_CHECK(s.equal(expect)); + uintptr_t idx = 1; // skipping the magic byte eterm t1((const char*)expect, idx, sizeof(expect)); - BOOST_REQUIRE_EQUAL(d, t1.to_long()); + BOOST_CHECK_EQUAL(d, t1.to_long()); } { +#if EIXX_SIZEOF_LONG >= 8 long d = 12345678901; +#else + long d = 0x12345678; +#endif // EIXX_SIZEOF_LONG >= 8 eterm t(d); string s(t.encode(0)); +#if EIXX_SIZEOF_LONG >= 8 const uint8_t expect[] = {131,110,5,0,53,28,220,223,2}; - BOOST_REQUIRE(s.equal(expect)); - int idx = 1; // skipping the magic byte +#else + const uint8_t expect[] = {131,110,4,0,0x78,0x56,0x34,0x12}; +#endif // EIXX_SIZEOF_LONG >= 8 + BOOST_CHECK(s.equal(expect)); + uintptr_t idx = 1; // skipping the magic byte eterm t1((const char*)expect, idx, sizeof(expect)); - BOOST_REQUIRE_EQUAL(d, t1.to_long()); + BOOST_CHECK_EQUAL(d, t1.to_long()); } } @@ -151,60 +165,61 @@ BOOST_AUTO_TEST_CASE( test_encode_pid ) { { eterm t(epid("test@host", 1, 2, 0)); - BOOST_REQUIRE_EQUAL("#Pid", t.to_string()); + BOOST_CHECK_EQUAL("#Pid", t.to_string()); string s(t.encode(0)); //std::cout << s.to_binary_string() << std::endl; const uint8_t expect[] = - {131,103,100,0,9,116,101,115,116,64,104,111,115,116,0,0,0,1,0,0,0,2,0}; - BOOST_REQUIRE(s.equal(expect)); - int idx = 1; // skipping the magic byte + {131,88,118,0,9,116,101,115,116,64,104,111,115,116,0,0,0,1,0,0,0,2,0,0,0,0}; + BOOST_CHECK(s.equal(expect)); + uintptr_t idx = 1; // skipping the magic byte eterm pid(epid((const char*)expect, idx, sizeof(expect))); - BOOST_REQUIRE_EQUAL(eterm(pid), t); + BOOST_CHECK_EQUAL(eterm(pid), t); } { const uint8_t expect[] = - {131,103,100,0,8,97,98,99,64,102,99,49,50,0,0,0,96,0,0,0,0,3}; - int idx = 1; // skipping the magic byte + {131,88,118,0,9,116,101,115,116,64,104,111,115,116,0,0,0,1,0,0,0,2,0,0,0,3}; + uintptr_t idx = 1; // skipping the magic byte epid decode_pid((const char*)expect, idx, sizeof(expect)); - epid expect_pid("abc@fc12", 96, 0, 3); - BOOST_REQUIRE_EQUAL(expect_pid, decode_pid); + epid expect_pid("test@host", 1, 2, 3); + BOOST_CHECK_EQUAL(expect_pid, decode_pid); } } BOOST_AUTO_TEST_CASE( test_encode_port ) { eterm t(port("test@host", 1, 0)); - BOOST_REQUIRE_EQUAL("#Port", t.to_string()); + BOOST_CHECK_EQUAL("#Port", t.to_string()); string s(t.encode(0)); //std::cout << s.to_binary_string() << std::endl; const uint8_t expect[] = - {131,102,100,0,9,116,101,115,116,64,104,111,115,116,0,0,0,1,0}; - BOOST_REQUIRE(s.equal(expect)); - int idx = 1; // skipping the magic byte + {131,102,ERL_ATOM_UTF8_EXT,0,9,116,101,115,116,64,104,111,115,116,0,0,0,1,0}; + BOOST_CHECK(s.equal(expect)); + uintptr_t idx = 1; // skipping the magic byte eterm t1((const char*)expect, idx, sizeof(expect)); - BOOST_REQUIRE_EQUAL(t1, t); + BOOST_CHECK_EQUAL(t1, t); } BOOST_AUTO_TEST_CASE( test_encode_ref ) { uint32_t ids[] = {1,2,3}; eterm t(ref("test@host", ids, 0)); - BOOST_REQUIRE_EQUAL("#Ref", t.to_string()); + BOOST_CHECK_EQUAL("#Ref", t.to_string()); string s(t.encode(0)); //std::cout << s.to_binary_string() << std::endl; - const uint8_t expect[] = {131,114,0,3,100,0,9,116,101,115,116,64,104,111,115, - 116,0,0,0,0,1,0,0,0,2,0,0,0,3}; - BOOST_REQUIRE(s.equal(expect)); - int idx = 1; // skipping the magic byte + const uint8_t expect[] = + {131,90,0,3,100,0,9,116,101,115,116,64,104,111,115,116,0,0,0,0,0,0,0,1,0,0,0,2,0,0,0,3}; + BOOST_CHECK(s.equal(expect)); + uintptr_t idx = 1; // skipping the magic byte ref t1((const char*)expect, idx, sizeof(expect)); - BOOST_REQUIRE_EQUAL(eterm(t1), t); + BOOST_CHECK_EQUAL(eterm(t1), t); { - ref t(atom("abc@fc12"), 993, 0, 0, 2); - const uint8_t expect[] = - {131,114,0,3,100,0,8,97,98,99,64,102,99,49,50,2,0,0,3,225,0,0,0,0,0,0,0,0}; - int idx = 1; // skipping the magic byte - ref t1((const char*)expect, idx, sizeof(expect)); - BOOST_REQUIRE_EQUAL(t1, t); + ref t2(atom("abc@fc12"), 993, 0, 0, 2); + //std::cout << string(eterm(t).encode(0)).to_binary_string() << std::endl; + const uint8_t expect2[] = + {131,90,0,3,118,0,8,97,98,99,64,102,99,49,50,0,0,0,2,0,0,3,225,0,0,0,0,0,0,0,0}; + uintptr_t idx2 = 1; // skipping the magic byte + ref t3((const char*)expect2, idx2, sizeof(expect2)); + BOOST_CHECK_EQUAL(t2, t3); } } @@ -227,14 +242,14 @@ BOOST_AUTO_TEST_CASE( test_encode_tuple ) eterm t(tup); string s(t.encode(0)); //std::cout << s.to_binary_string() << std::endl; - const uint8_t expect[] = {131,104,5,100,0,3,97,98,99,107,0,2,101,102,97,1, - 104,4,100,0,1,97,107,0,2,120,120,70,64,94,198,102, + const uint8_t expect[] = {131,104,5,ERL_ATOM_UTF8_EXT,0,3,97,98,99,107,0,2,101,102,97,1, + 104,4,ERL_ATOM_UTF8_EXT,0,1,97,107,0,2,120,120,70,64,94,198,102, 102,102,102,102,97,5,107,0,2,103,104}; - BOOST_REQUIRE(s.equal(expect)); - int idx = 1; // skipping the magic byte + BOOST_CHECK(s.equal(expect)); + uintptr_t idx = 1; // skipping the magic byte tuple t1((const char*)expect, idx, sizeof(expect)); - BOOST_REQUIRE_EQUAL(5ul, t1.size()); - BOOST_REQUIRE_EQUAL("{abc,\"ef\",1,{a,\"xx\",123.1,5},\"gh\"}", eterm(t1).to_string()); + BOOST_CHECK_EQUAL(5ul, t1.size()); + BOOST_CHECK_EQUAL("{abc,\"ef\",1,{a,\"xx\",123.1,5},\"gh\"}", eterm(t1).to_string()); } BOOST_AUTO_TEST_CASE( test_encode_trace ) @@ -243,30 +258,30 @@ BOOST_AUTO_TEST_CASE( test_encode_trace ) trace tr(1,2,3,self,4); eterm t(tr); string s(t.encode(0)); - //std::cout << s.to_binary_string() << std::endl; - const uint8_t expect[] = {131,104,5,97,1,97,2,97,3,103,100,0,8,97,98,99,64, - 102,99,49,50,0,0,0,96,0,0,0,0,3,97,4}; - BOOST_REQUIRE(s.equal(expect)); - int idx = 1; // skipping the magic byte + //std::cout << to_binary_string(s) << std::endl; + const uint8_t expect[] = + {131,104,5,97,1,97,2,97,3,88,118,0,8,97,98,99,64,102,99,49,50,0,0,0,96,0,0,0,0,0,0,0,3,97,4}; + BOOST_CHECK(s.equal(expect)); + uintptr_t idx = 1; // skipping the magic byte trace t1((const char*)expect, idx, sizeof(expect)); - BOOST_REQUIRE_EQUAL(5ul, t1.size()); - BOOST_REQUIRE_EQUAL(1, t1.flags()); - BOOST_REQUIRE_EQUAL(2, t1.label()); - BOOST_REQUIRE_EQUAL(3, t1.serial()); - BOOST_REQUIRE(self == t1.from()); - BOOST_REQUIRE_EQUAL(4, t1.prev()); - BOOST_REQUIRE_EQUAL("{1,2,3,#Pid,4}", eterm(t1).to_string()); + BOOST_CHECK_EQUAL(5ul, t1.size()); + BOOST_CHECK_EQUAL(1, t1.flags()); + BOOST_CHECK_EQUAL(2, t1.label()); + BOOST_CHECK_EQUAL(3, t1.serial()); + BOOST_CHECK(self == t1.from()); + BOOST_CHECK_EQUAL(4, t1.prev()); + BOOST_CHECK_EQUAL("{1,2,3,#Pid,4}", eterm(t1).to_string()); } BOOST_AUTO_TEST_CASE( test_encode_rpc ) { - static const char s_expected[] = { - 131,104,2,103,100,0,14,69,67,71,46,72,49,46,48,48,49,64,102,49,54,0,0,0,1, - 0,0,0,0,0,104,5,100,0,4,99,97,108,108,100,0,7,101,99,103,95,97,112,105,100, - 0,11,114,101,103,95,112,114,111,99,101,115,115,108,0,0,0,5,100,0,3,69,67,71, - 100,0,10,69,67,71,46,72,49,46,48,48,49,103,100,0,14,69,67,71,46,72,49,46,48, - 48,49,64,102,49,54,0,0,0,1,0,0,0,0,0,107,0,12,101,120,97,109,112,108,101,95, - 99,111,114,101,98,0,0,7,208,106,100,0,4,117,115,101,114 + static const unsigned char s_expected[] = { + 131,104,2,88,118,0,14,69,67,71,46,72,49,46,48,48,49,64,102,49,54,0,0,0,1,0, + 0,0,0,0,0,0,0,104,5,118,0,4,99,97,108,108,118,0,7,101,99,103,95,97,112,105, + 118,0,11,114,101,103,95,112,114,111,99,101,115,115,108,0,0,0,5,118,0,3,69, + 67,71,118,0,10,69,67,71,46,72,49,46,48,48,49,88,118,0,14,69,67,71,46,72,49, + 46,48,48,49,64,102,49,54,0,0,0,1,0,0,0,0,0,0,0,0,107,0,12,101,120,97,109, + 112,108,101,95,99,111,114,101,98,0,0,7,208,106,118,0,4,117,115,101,114 }; epid l_pid("ECG.H1.001@f16", 1, 0, 0); @@ -281,12 +296,12 @@ BOOST_AUTO_TEST_CASE( test_encode_rpc ) // ['ECG','ECG.H1.001',#Pid<'ECG.H1.001@f16'.1.0.0>,"example_core",2000],user}} char l_buf[256]; size_t l_sz = l_term.encode_size(0, true); - BOOST_REQUIRE(l_sz < sizeof(l_buf)); + BOOST_CHECK(l_sz < sizeof(l_buf)); l_term.encode(l_buf, l_sz, 0, true); std::string s = std::string(l_buf, l_sz); std::string s_exp = std::string(l_buf, sizeof(s_expected)); if (s != s_exp) std::cout << "String: " << to_binary_string(s.c_str(), s.size()) << std::endl; - BOOST_REQUIRE_EQUAL(s_exp, s); + BOOST_CHECK_EQUAL(s_exp, s); } diff --git a/test/test_eterm_format.cpp b/test/test_eterm_format.cpp index 330ea07..6207ade 100644 --- a/test/test_eterm_format.cpp +++ b/test/test_eterm_format.cpp @@ -1,23 +1,19 @@ /* ***** BEGIN LICENSE BLOCK ***** -This file is part of the EPI (Erlang Plus Interface) Library. +Copyright 2010 Serge Aleynikov -Copyright (C) 2010 Serge Aleynikov +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. + http://www.apache.org/licenses/LICENSE-2.0 -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. ***** END LICENSE BLOCK ***** */ @@ -26,7 +22,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include #include -using namespace EIXX_NAMESPACE; +using namespace eixx; BOOST_AUTO_TEST_CASE( test_eterm_format_string ) { @@ -38,6 +34,31 @@ BOOST_AUTO_TEST_CASE( test_eterm_format_string ) BOOST_REQUIRE_EQUAL("abc", et.to_str()); } +BOOST_AUTO_TEST_CASE( test_eterm_format_binary ) +{ + allocator_t alloc; + + eterm et = eterm::format(alloc, "<<\"abc\">>"); + + BOOST_REQUIRE_EQUAL(BINARY, et.type()); + BOOST_REQUIRE_EQUAL("abc", std::string(et.to_binary().data(), et.to_binary().size())); + + et = eterm::format(alloc, "<<65,66, 67>>"); + BOOST_REQUIRE_EQUAL(BINARY, et.type()); + BOOST_REQUIRE_EQUAL("ABC", std::string(et.to_binary().data(), et.to_binary().size())); + + et = eterm::format(alloc, "<<>>"); + BOOST_REQUIRE_EQUAL(BINARY, et.type()); + BOOST_REQUIRE_EQUAL("", std::string(et.to_binary().data(), et.to_binary().size())); + + et = eterm::format(alloc, "<<\"\">>"); + BOOST_REQUIRE_EQUAL(BINARY, et.type()); + BOOST_REQUIRE_EQUAL("", std::string(et.to_binary().data(), et.to_binary().size())); + + BOOST_CHECK_THROW(eterm::format("<<-1>>"), err_format_exception); + BOOST_CHECK_THROW(eterm::format("<<1,2 3>>"), err_format_exception); +} + BOOST_AUTO_TEST_CASE( test_eterm_format_atom ) { allocator_t alloc; @@ -102,8 +123,8 @@ BOOST_AUTO_TEST_CASE( test_eterm_format_const ) { allocator_t alloc; - eterm et = eterm::format(alloc, - "[~i, 10, 2.5, abc, \"efg\", {~f, ~i}, ~a]", + eterm et = eterm::format(alloc, + "[~i, 10, 2.5, abc, \"efg\", {~f, ~i}, ~a]", 1, 2.1, 10, "xx"); BOOST_REQUIRE_EQUAL(LIST, et.type()); @@ -124,4 +145,67 @@ BOOST_AUTO_TEST_CASE( test_eterm_format_compound ) BOOST_REQUIRE_EQUAL("[1,[{\"ab\",2},{xx,3}],{2.1,10},xyz,abc]", et.to_string()); } +BOOST_AUTO_TEST_CASE( test_eterm_var_type ) +{ + BOOST_REQUIRE_EQUAL(UNDEFINED, eterm::format("B").to_var().type()); + BOOST_REQUIRE_EQUAL(LONG, eterm::format("B::int()").to_var().type()); + BOOST_REQUIRE_EQUAL(LONG, eterm::format("B::byte()").to_var().type()); + BOOST_REQUIRE_EQUAL(LONG, eterm::format("B::char()").to_var().type()); + BOOST_REQUIRE_EQUAL(LONG, eterm::format("B::integer()").to_var().type()); + BOOST_REQUIRE_EQUAL(STRING, eterm::format("B::string()").to_var().type()); + BOOST_REQUIRE_EQUAL(ATOM, eterm::format("B::atom()").to_var().type()); + BOOST_REQUIRE_EQUAL(DOUBLE, eterm::format("B::float()").to_var().type()); + BOOST_REQUIRE_EQUAL(DOUBLE, eterm::format("B::double()").to_var().type()); + BOOST_REQUIRE_EQUAL(BINARY, eterm::format("B::binary()").to_var().type()); + BOOST_REQUIRE_EQUAL(BOOL, eterm::format("B::bool()").to_var().type()); + BOOST_REQUIRE_EQUAL(BOOL, eterm::format("B::boolean()").to_var().type()); + BOOST_REQUIRE_EQUAL(LIST, eterm::format("B::list()").to_var().type()); + BOOST_REQUIRE_EQUAL(TUPLE, eterm::format("B::tuple()").to_var().type()); + BOOST_REQUIRE_EQUAL(PID, eterm::format("B::pid()").to_var().type()); + BOOST_REQUIRE_EQUAL(REF, eterm::format("B::ref()").to_var().type()); + BOOST_REQUIRE_EQUAL(REF, eterm::format("B::reference()").to_var().type()); + BOOST_REQUIRE_EQUAL(PORT, eterm::format("B::port()").to_var().type()); +} +BOOST_AUTO_TEST_CASE( test_eterm_mfa_format ) +{ + atom m, f; + eterm args; + + BOOST_REQUIRE_NO_THROW(eterm::format(m, f, args, "a:b()")); + BOOST_REQUIRE_NO_THROW(eterm::format(m, f, args, "a:b().")); + BOOST_REQUIRE_NO_THROW(eterm::format(m, f, args, "a:b()\t")); + BOOST_REQUIRE_NO_THROW(eterm::format(m, f, args, "a:b()\t .")); + BOOST_REQUIRE_NO_THROW(eterm::format(m, f, args, "a:b() ")); + BOOST_REQUIRE_NO_THROW(eterm::format(m, f, args, "a:b()\n.")); + BOOST_REQUIRE_NO_THROW(eterm::format(m, f, args, "a:b( %comment\n).")); + BOOST_REQUIRE_NO_THROW(eterm::format(m, f, args, "a:b().%comment")); + BOOST_REQUIRE_NO_THROW(eterm::format(m, f, args, "a:b(10)")); + BOOST_REQUIRE_NO_THROW(eterm::format(m, f, args, "a:b(10).")); + BOOST_REQUIRE_NO_THROW(eterm::format(m, f, args, "aa:bb(10)")); + BOOST_REQUIRE_NO_THROW(eterm::format(m, f, args, "a:b(10,20).")); + BOOST_REQUIRE_NO_THROW(eterm::format(m, f, args, "a:b(~i).", 10)); + BOOST_REQUIRE_NO_THROW(eterm::format(m, f, args, "a:b(~f,~i).", 20.0, 10)); + BOOST_REQUIRE_NO_THROW(eterm::format(m, f, args, "a:b([~i,1], {ok,'a'}).", 10)); +} + +BOOST_AUTO_TEST_CASE( test_eterm_mfa_format_bad ) +{ + atom m, f; + eterm args; + + BOOST_REQUIRE_THROW(eterm::format(m, f, args, "a:b(1, %comment\n"), err_format_exception); + BOOST_REQUIRE_THROW(eterm::format(m, f, args, "a:b(1, %comment 2)."), err_format_exception); + BOOST_REQUIRE_THROW(eterm::format(m, f, args, "("), err_format_exception); + BOOST_REQUIRE_THROW(eterm::format(m, f, args, ")"), err_format_exception); + BOOST_REQUIRE_THROW(eterm::format(m, f, args, "."), err_format_exception); + BOOST_REQUIRE_THROW(eterm::format(m, f, args, "aa"), err_format_exception); + BOOST_REQUIRE_THROW(eterm::format(m, f, args, "a("), err_format_exception); + BOOST_REQUIRE_THROW(eterm::format(m, f, args, "a:b("), err_format_exception); + BOOST_REQUIRE_THROW(eterm::format(m, f, args, "a.b()"), err_format_exception); + BOOST_REQUIRE_THROW(eterm::format(m, f, args, "a:b(10 20)"), err_format_exception); + BOOST_REQUIRE_THROW(eterm::format(m, f, args, "a:b(10. 20)"), err_format_exception); + BOOST_REQUIRE_THROW(eterm::format(m, f, args, "a:b(10.(20)"), err_format_exception); + BOOST_REQUIRE_THROW(eterm::format(m, f, args, "a:b(~i,~i]", 10, 20), err_format_exception); + BOOST_REQUIRE_THROW(eterm::format(m, f, args, "a:b([[~i,20],]", 10), err_format_exception); +} diff --git a/test/test_eterm_match.cpp b/test/test_eterm_match.cpp index fdbdb25..dde860a 100644 --- a/test/test_eterm_match.cpp +++ b/test/test_eterm_match.cpp @@ -1,33 +1,29 @@ /* ***** BEGIN LICENSE BLOCK ***** -This file is part of the EPI (Erlang Plus Interface) Library. +Copyright 2010 Serge Aleynikov -Copyright (C) 2010 Serge Aleynikov +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. + http://www.apache.org/licenses/LICENSE-2.0 -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. ***** END LICENSE BLOCK ***** */ #include -#include +#include #include "test_alloc.hpp" #include -using namespace EIXX_NAMESPACE; +using namespace eixx; BOOST_AUTO_TEST_CASE( test_match1 ) { @@ -53,19 +49,25 @@ BOOST_AUTO_TEST_CASE( test_match1 ) list ll(2, alloc); ll.push_back(eterm(1)); - ll.push_back(eterm(var())); + ll.push_back(eterm(var(LONG))); ll.close(); - // Add pattern = {test, _, [1, _]} + // Add pattern = {test, _::int(), [1, _::int()]} tuple ptup(3, alloc); ptup.push_back(eterm(atom("test"))); - ptup.push_back(var()); + ptup.push_back(var(LONG)); ptup.push_back(ll); eterm pattern(ptup); // Perform pattern match on the tuple bool res = tup.match(pattern); BOOST_REQUIRE(res); + + varbind vb; + res = tup.match(pattern, &vb); + BOOST_REQUIRE(res); + + BOOST_REQUIRE_EQUAL(0u, vb.count()); } } @@ -74,13 +76,13 @@ namespace { int match[10]; cb_t() { bzero(match, sizeof(match)); } - bool operator() (const eterm& a_pattern, + bool operator() (const eterm& /*a_pattern*/, const varbind& a_varbind, long a_opaque) { const eterm* n_var = a_varbind.find("N"); if (n_var && n_var->type() == LONG) { - int n = n_var->to_long(); + long n = n_var->to_long(); switch (a_opaque) { case 1: match[0]++; @@ -104,10 +106,12 @@ namespace { BOOST_REQUIRE(a_varbind.find("X") != NULL); BOOST_REQUIRE_EQUAL(TUPLE, a_varbind.find("X")->type()); break; + case 5: + match[4]++; + break; default: throw "Invalid opaque value!"; } - return true; } return true; } @@ -129,32 +133,45 @@ BOOST_AUTO_TEST_CASE( test_match2 ) // Each pattern contains a variable N that will be bound with // the "pattern number" in each successful match. etm.push_back(eterm::format("{test, N, A}"), - boost::bind(&cb_t::operator(), &cb, _1, _2, _3), 1); + std::bind(&cb_t::operator(), &cb, + std::placeholders::_1, std::placeholders::_2, std::placeholders::_3), 1); etm.push_back(eterm::format("{ok, N, B, _}"), - boost::bind(&cb_t::operator(), &cb, _1, _2, _3), 2); + std::bind(&cb_t::operator(), &cb, + std::placeholders::_1, std::placeholders::_2, std::placeholders::_3), 2); etm.push_back(eterm::format("{error, N, Reason}"), - boost::bind(&cb_t::operator(), &cb, _1, _2, _3), 3); + std::bind(&cb_t::operator(), &cb, + std::placeholders::_1, std::placeholders::_2, std::placeholders::_3), 3); // Remember the reference to last pattern. We'll try to delete it later. const eterm_pattern_action& action = etm.push_back(eterm::format("{xxx, [_, _, {c, N}], \"abc\", X}"), - boost::bind(&cb_t::operator(), &cb, _1, _2, _3), 4); + std::bind(&cb_t::operator(), &cb, + std::placeholders::_1, std::placeholders::_2, std::placeholders::_3), 4); + etm.push_back(eterm::format("{login, I::int()}")); // Make sure we registered 4 pattern above. - BOOST_REQUIRE_EQUAL(4u, etm.size()); + BOOST_REQUIRE_EQUAL(5u, etm.size()); + + static const eterm t0 = atom("test"); + eterm f0 = eterm::format("test"); + bool m0 = t0.match(f0); + BOOST_REQUIRE(m0); // N = 1 // Match the following terms against registered patters. - BOOST_REQUIRE(etm.match(eterm::format("{test, 1, 123}"))); // N = 1 - BOOST_REQUIRE(etm.match(eterm::format("{test, 1, 234}"))); // N = 1 + auto f1 = eterm::format("{test, 1, 123}"); + auto m1 = etm.match(f1); + BOOST_REQUIRE(m1); // N = 1 + BOOST_REQUIRE_EQUAL(1, etm.match(eterm::format("{test, 1, 234}"))); // N = 1 - BOOST_REQUIRE(etm.match(eterm::format("{ok, 2, 3, 4}"))); // N = 2 - BOOST_REQUIRE(!etm.match(eterm::format("{ok, 2}"))); + BOOST_REQUIRE_EQUAL(2, etm.match(eterm::format("{ok, 2, 3, 4}"))); // N = 2 + BOOST_REQUIRE_EQUAL(0, etm.match(eterm::format("{ok, 2}"))); - BOOST_REQUIRE(etm.match(eterm::format("{error, 3, not_found}"))); // N = 3 + BOOST_REQUIRE_EQUAL(3, etm.match(eterm::format("{error, 3, not_found}"))); // N = 3 - BOOST_REQUIRE(etm.match( + BOOST_REQUIRE_EQUAL(4, etm.match( eterm::format("{xxx, [{a, 1}, {b, 2}, {c, 4}], \"abc\", {5,6,7}}"))); // N = 4 - BOOST_REQUIRE(!etm.match( + BOOST_REQUIRE_EQUAL(0, etm.match( eterm::format("{xxx, [1, 2, 3, {c, 4}], \"abc\", 5}"))); + BOOST_REQUIRE_EQUAL(5, etm.match(eterm::format("{login, 1}"))); // Verify number of successful matches for each pattern. BOOST_REQUIRE_EQUAL(2, cb.match[0]); @@ -162,20 +179,25 @@ BOOST_AUTO_TEST_CASE( test_match2 ) BOOST_REQUIRE_EQUAL(1, cb.match[2]); BOOST_REQUIRE_EQUAL(1, cb.match[3]); - // Make sure we registered 4 pattern above. - BOOST_REQUIRE_EQUAL(4u, etm.size()); - // Test pattern deletion. etm.erase(action); // Make sure we registered 4 pattern above. - BOOST_REQUIRE_EQUAL(3u, etm.size()); + BOOST_REQUIRE_EQUAL(4u, etm.size()); } BOOST_AUTO_TEST_CASE( test_match3 ) { { - const eterm e = eterm::format("{snap, x12, []}"); - const eterm p = eterm::format("{snap, N, L}"); + auto p = eterm::format("{ok, N, A}"); + auto e = eterm::format("{ok, 1, 2}"); + varbind b; + auto m = e.match(p, &b); + BOOST_REQUIRE(m); + } + + { + auto e = eterm::format("{snap, x12, []}"); + auto p = eterm::format("{snap, N, L}"); varbind binding; BOOST_REQUIRE(p.match(e, &binding)); const eterm* n = binding.find("N"); @@ -187,17 +209,47 @@ BOOST_AUTO_TEST_CASE( test_match3 ) BOOST_REQUIRE_EQUAL(atom("x12"), n->to_atom()); BOOST_REQUIRE_EQUAL(list(), l->to_list()); } + + { + auto e = eterm::format("{1, 8#16, $a, 'Xbc', [{x, 2.0}]}"); + auto p = eterm::format("{A::int(), B::int(), C::char(), Q::atom(), D::list()}"); + varbind b; + bool m = e.match(p, &b); + BOOST_REQUIRE(m); + auto va = b.find("A"); + auto vb = b.find("B"); + auto vc = b.find("C"); + auto vd = b.find("D"); + auto vq = b.find("Q"); + BOOST_REQUIRE(va); + BOOST_REQUIRE(vb); + BOOST_REQUIRE(vc); + BOOST_REQUIRE(vd); + BOOST_REQUIRE_EQUAL(LONG, va->type()); + BOOST_REQUIRE_EQUAL(1, va->to_long()); + BOOST_REQUIRE_EQUAL(LONG, vb->type()); + BOOST_REQUIRE_EQUAL(14, vb->to_long()); + BOOST_REQUIRE_EQUAL(LONG, vc->type()); + BOOST_REQUIRE_EQUAL('a', vc->to_long()); + BOOST_REQUIRE_EQUAL(ATOM, vq->type()); + BOOST_REQUIRE_EQUAL(atom("Xbc"), vq->to_atom()); + BOOST_REQUIRE_EQUAL(LIST, vd->type()); + BOOST_REQUIRE_EQUAL(1u, vd->to_list().length()); + m = eterm::format("[{x, 2.0}]").match(*vd); + BOOST_REQUIRE(m); + } + { - const eterm t = eterm::format("[1,a,$b,\"xyz\",{1,10.0},[]]"); - const eterm pat = eterm::format("[A,B,C,D,E,F]"); + auto t = eterm::format("[1,a,$b,\"xyz\",{1,10.0},[]]"); + auto pat = eterm::format("[A,B,C,D,E,F]"); varbind p; BOOST_REQUIRE(pat.match(t, &p)); - const eterm* a = p.find("A"); - const eterm* b = p.find("B"); - const eterm* c = p.find("C"); - const eterm* d = p.find("D"); - const eterm* e = p.find("E"); - const eterm* f = p.find("F"); + auto a = p.find("A"); + auto b = p.find("B"); + auto c = p.find("C"); + auto f = p.find("F"); + auto d = p.find("D"); + auto e = p.find("E"); BOOST_REQUIRE(a); BOOST_REQUIRE(b); BOOST_REQUIRE(c); @@ -213,6 +265,78 @@ BOOST_AUTO_TEST_CASE( test_match3 ) } } +BOOST_AUTO_TEST_CASE( test_initializer_list ) +{ + const atom am_abc("abc"); + + { + eterm t{1, 20.0, am_abc, "xxx"}; + BOOST_REQUIRE_EQUAL(TUPLE, t.type()); + BOOST_REQUIRE_EQUAL(4u, t.to_tuple().size()); + BOOST_REQUIRE_EQUAL(1, t.to_tuple()[0].to_long()); + BOOST_REQUIRE_EQUAL(20.0, t.to_tuple()[1].to_double()); + BOOST_REQUIRE_EQUAL(am_abc, t.to_tuple()[2].to_atom()); + BOOST_REQUIRE_EQUAL("xxx", t.to_tuple()[3].to_str()); + + t = {eterm(1), eterm(20.0), eterm(atom("abc")), eterm("xxx")}; + BOOST_REQUIRE_EQUAL(TUPLE, t.type()); + BOOST_REQUIRE_EQUAL(4u, t.to_tuple().size()); + BOOST_REQUIRE_EQUAL(1, t.to_tuple()[0].to_long()); + BOOST_REQUIRE_EQUAL(20.0, t.to_tuple()[1].to_double()); + BOOST_REQUIRE_EQUAL(am_abc, t.to_tuple()[2].to_atom()); + BOOST_REQUIRE_EQUAL("xxx", t.to_tuple()[3].to_str()); + + t = {1, 20.0, am_abc, "xxx", {12, am_abc}}; + BOOST_REQUIRE_EQUAL(TUPLE, t.type()); + BOOST_REQUIRE_EQUAL(5u, t.to_tuple().size()); + BOOST_REQUIRE_EQUAL(1, t.to_tuple()[0].to_long()); + BOOST_REQUIRE_EQUAL(20.0, t.to_tuple()[1].to_double()); + BOOST_REQUIRE_EQUAL(am_abc, t.to_tuple()[2].to_atom()); + BOOST_REQUIRE_EQUAL(2u, t.to_tuple()[4].to_tuple().size()); + BOOST_REQUIRE_EQUAL(12, t.to_tuple()[4].to_tuple()[0].to_long()); + BOOST_REQUIRE_EQUAL(am_abc, t.to_tuple()[4].to_tuple()[1].to_atom()); + } + + { + eterm t = list{1, 20.0, am_abc, "xxx"}; + BOOST_REQUIRE_EQUAL(LIST, t.type()); + BOOST_REQUIRE_EQUAL(4u, t.to_list().length()); + auto it = t.to_list().begin(); + BOOST_REQUIRE_EQUAL(1, it->to_long()); + BOOST_REQUIRE_EQUAL(20.0, (++it)->to_double()); + BOOST_REQUIRE_EQUAL(am_abc, (++it)->to_atom()); + BOOST_REQUIRE_EQUAL("xxx", (++it)->to_str()); + + t = list{eterm(1), eterm(20.0), eterm(atom("abc")), eterm("xxx")}; + it = t.to_list().begin(); + BOOST_REQUIRE_EQUAL(LIST, t.type()); + BOOST_REQUIRE_EQUAL(4u, t.to_list().length()); + BOOST_REQUIRE_EQUAL(1, it->to_long()); + BOOST_REQUIRE_EQUAL(20.0, (++it)->to_double()); + BOOST_REQUIRE_EQUAL(am_abc, (++it)->to_atom()); + BOOST_REQUIRE_EQUAL("xxx", (++it)->to_str()); + + t = list{1, 20.0, am_abc, "xxx", {12, am_abc}, list{3,4}}; + it = t.to_list().begin(); + BOOST_REQUIRE_EQUAL(LIST, t.type()); + BOOST_REQUIRE_EQUAL(6u, t.to_list().length()); + BOOST_REQUIRE_EQUAL(1, it->to_long()); + BOOST_REQUIRE_EQUAL(20.0, (++it)->to_double()); + BOOST_REQUIRE_EQUAL(am_abc, (++it)->to_atom()); + BOOST_REQUIRE_EQUAL("xxx", (++it)->to_str()); + BOOST_REQUIRE_EQUAL(TUPLE, (++it)->type()); + auto tt = it->to_tuple(); + BOOST_REQUIRE_EQUAL(2u, tt.size()); + BOOST_REQUIRE_EQUAL(12, tt[0].to_long()); + BOOST_REQUIRE_EQUAL(am_abc, tt[1].to_atom()); + BOOST_REQUIRE_EQUAL(LIST, (++it)->type()); + auto l = it->to_list().begin(); + BOOST_REQUIRE_EQUAL(2u, it->to_list().length()); + BOOST_REQUIRE_EQUAL(3, (l++)->to_long()); + BOOST_REQUIRE_EQUAL(4, (l++)->to_long()); + } +} + static void run(int n) { static const int iterations = ::getenv("ITERATIONS") ? atoi(::getenv("ITERATIONS")) : 1; @@ -225,13 +349,15 @@ static void run(int n) { eterm(12345), 4 } }; - static eterm_pattern_matcher sp(list, boost::ref(cb), alloc); + static eterm_pattern_matcher sp(list, std::ref(cb), alloc); BOOST_REQUIRE_EQUAL(n == 1 ? 4u : 3u, sp.size()); for(int i=0; i < iterations; i++) { BOOST_REQUIRE(!sp.match(eterm::format(alloc, "{ok, 1, 3, 4}"))); - BOOST_REQUIRE_EQUAL((n == 1), sp.match(eterm::format(alloc, "{ok, 1, 2}"))); // N = 1 + auto f1 = eterm::format(alloc, "{ok, 1, 2}"); + auto m1 = sp.match(f1); + BOOST_REQUIRE_EQUAL((n == 1), m1); // N = 1 BOOST_REQUIRE(sp.match(eterm::format(alloc, "{error, 2, not_found}"))); // N = 2 BOOST_REQUIRE(!sp.match(eterm::format(alloc, "{test, 3}"))); @@ -288,17 +414,22 @@ BOOST_AUTO_TEST_CASE( test_match_list ) BOOST_AUTO_TEST_CASE( test_match_list_tail ) { BOOST_WARN_MESSAGE(false, "SKIPPING test_match_list_tail - needs extension to list matching!"); + BOOST_REQUIRE(true); // don't print the warning return; + static const atom A("A"); + static const atom O("O"); + static const atom T("T"); + eterm pattern = eterm::format("[{A, O} | T]"); eterm term = eterm::format("[{a, 1}, {b, ok}, {c, 2.0}]"); varbind vars; // Match [{a, 1} | T] BOOST_REQUIRE(pattern.match(term, &vars)); - const eterm* name = vars.find("A"); - const eterm* value = vars.find("O"); - const eterm* tail = vars.find("T"); + const eterm* name = vars.find(A); + const eterm* value = vars.find(O); + const eterm* tail = vars.find(T); BOOST_REQUIRE(name != NULL); BOOST_REQUIRE(value != NULL); @@ -312,9 +443,9 @@ BOOST_AUTO_TEST_CASE( test_match_list_tail ) // Match [{b, ok} | T] vars.clear(); BOOST_REQUIRE(term.match(*tail, &vars)); - name = vars.find("A"); - value = vars.find("O"); - tail = vars.find("T"); + name = vars.find(A); + value = vars.find(O); + tail = vars.find(T); BOOST_REQUIRE(name != NULL); BOOST_REQUIRE(value != NULL); @@ -328,9 +459,9 @@ BOOST_AUTO_TEST_CASE( test_match_list_tail ) // Match [{c, 2.0} | T] vars.clear(); BOOST_REQUIRE(term.match(*tail, &vars)); - name = vars.find("A"); - value = vars.find("O"); - tail = vars.find("T"); + name = vars.find(A); + value = vars.find(O); + tail = vars.find(T); BOOST_REQUIRE(name != NULL); BOOST_REQUIRE(value != NULL); @@ -342,3 +473,26 @@ BOOST_AUTO_TEST_CASE( test_match_list_tail ) BOOST_REQUIRE_EQUAL(LIST, tail->type()); } +BOOST_AUTO_TEST_CASE( test_eterm_var_match ) +{ + BOOST_REQUIRE(eterm(1) .match(eterm::format("B"))); + BOOST_REQUIRE(eterm(10) .match(eterm::format("B::int()"))); + BOOST_REQUIRE(eterm('c') .match(eterm::format("B::int()"))); + BOOST_REQUIRE(eterm('c') .match(eterm::format("B::byte()"))); + BOOST_REQUIRE(eterm('c') .match(eterm::format("B::char()"))); + BOOST_REQUIRE(eterm("abc") .match(eterm::format("B"))); + BOOST_REQUIRE(eterm("abc") .match(eterm::format("B::string()"))); + BOOST_REQUIRE(eterm(atom("abc")) .match(eterm::format("B"))); + BOOST_REQUIRE(eterm(atom("abc")) .match(eterm::format("B::atom()"))); + BOOST_REQUIRE(eterm(10.123) .match(eterm::format("B::float()"))); + BOOST_REQUIRE(eterm(10.123) .match(eterm::format("B::double()"))); + BOOST_REQUIRE(eterm(binary{1,2,3}) .match(eterm::format("B::binary()"))); + BOOST_REQUIRE(eterm(true) .match(eterm::format("B::boolean()"))); + BOOST_REQUIRE(eterm(false) .match(eterm::format("B::bool()"))); + BOOST_REQUIRE(eterm(list{1,2.0,"a"}).match(eterm::format("B::list()"))); + BOOST_REQUIRE(eterm({1,2.0,"a"}) .match(eterm::format("B::tuple()"))); + BOOST_REQUIRE(eterm(epid()) .match(eterm::format("B::pid()"))); + BOOST_REQUIRE(eterm(port()) .match(eterm::format("B::port()"))); + BOOST_REQUIRE(eterm(ref()) .match(eterm::format("B::ref()"))); + BOOST_REQUIRE(eterm(ref()) .match(eterm::format("B::reference()"))); +} diff --git a/test/test_eterm_pool.cpp b/test/test_eterm_pool.cpp index 995e80b..97601a1 100644 --- a/test/test_eterm_pool.cpp +++ b/test/test_eterm_pool.cpp @@ -1,23 +1,19 @@ /* ***** BEGIN LICENSE BLOCK ***** -This file is part of the EPI (Erlang Plus Interface) Library. +Copyright 2010 Serge Aleynikov -Copyright (C) 2010 Serge Aleynikov +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. + http://www.apache.org/licenses/LICENSE-2.0 -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. ***** END LICENSE BLOCK ***** */ @@ -25,13 +21,13 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include "test_alloc.hpp" #include -using namespace EIXX_NAMESPACE; +using namespace eixx; BOOST_AUTO_TEST_CASE( test_encode_atom_t ) { atom a("abc"); string s(eterm(a).encode(0)); - const uint8_t expect[] = {131,100,0,3,97,98,99}; + const uint8_t expect[] = {131,ERL_ATOM_UTF8_EXT,0,3,97,98,99}; BOOST_REQUIRE(s.equal(expect)); } @@ -51,7 +47,7 @@ BOOST_AUTO_TEST_CASE( test_encode_double_t ) string s(t.encode(0)); const uint8_t expect[] = {131,70,64,200,28,214,230,49,248,161}; BOOST_REQUIRE(s.equal(expect)); - int idx = 1; // skipping the magic byte + uintptr_t idx = 1; // skipping the magic byte eterm t1((const char*)expect, idx, sizeof(expect)); BOOST_REQUIRE_EQUAL(d, t1.to_double()); } @@ -63,7 +59,7 @@ BOOST_AUTO_TEST_CASE( test_encode_long_t ) string s(t.encode(0)); const uint8_t expect[] = {131,97,123}; BOOST_REQUIRE(s.equal(expect)); - int idx = 1; // skipping the magic byte + uintptr_t idx = 1; // skipping the magic byte eterm t1((const char*)expect, idx, sizeof(expect)); BOOST_REQUIRE_EQUAL(d, t1.to_long()); } diff --git a/test/test_eterm_refc.cpp b/test/test_eterm_refc.cpp index 427c966..d4f9ecd 100644 --- a/test/test_eterm_refc.cpp +++ b/test/test_eterm_refc.cpp @@ -1,108 +1,138 @@ /* ***** BEGIN LICENSE BLOCK ***** -This file is part of the EPI (Erlang Plus Interface) Library. +Copyright 2010 Serge Aleynikov -Copyright (C) 2010 Serge Aleynikov +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. + http://www.apache.org/licenses/LICENSE-2.0 -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. ***** END LICENSE BLOCK ***** */ #include #include "test_alloc.hpp" #include +#include + +using namespace eixx; +using eixx::marshal::eterm; +using eixx::marshal::list; -using namespace EIXX_NAMESPACE; -using EIXX_NAMESPACE::marshal::eterm; +static int g_alloc_count; template struct counted_alloc : public std::allocator { - static int count; counted_alloc() {} - counted_alloc(const counted_alloc& a) {} + counted_alloc(const counted_alloc& /*a*/) {} template counted_alloc(const _T&) {} - typedef T* pointer; - typedef const T* const_pointer; - typedef T& reference; - typedef const T& const_reference; + using pointer = T*; + using const_pointer = T const*; + using reference = T&; + using const_reference = T const&; + using value_type = T; - template struct rebind { - typedef counted_alloc<_T> other; + template struct rebind { + using other = counted_alloc; }; void construct(pointer p, const T& value) { ::new((void*)p) T(value); } - - pointer allocate(size_t sz) { - count++; - return static_cast(::operator new(sz * sizeof(T))); + + pointer allocate(size_t n) { + ++g_alloc_count; + return static_cast(::operator new(n * sizeof(T))); } - void deallocate(pointer p, size_t sz) { - count--; + void deallocate(pointer p, size_t /*sz*/) { + --g_alloc_count; ::operator delete(p); } - void destroy(pointer __p) { __p->~T(); } + void destroy(pointer p) { p->~T(); } }; -template -int counted_alloc::count = 0; - BOOST_AUTO_TEST_CASE( test_refc_format ) { typedef counted_alloc my_alloc; my_alloc alloc; + + list lst(nullptr); // Allocates static global empty list + BOOST_CHECK_EQUAL(2, g_alloc_count); + { for (int i=0; i < 10; i++) { - eterm term = eterm::format(alloc, + BOOST_CHECK_EQUAL(2, g_alloc_count); + eterm term = eterm::format(alloc, "[~i, [{~s, ~i}, {~a, ~i}], {~f, ~i}, ~a]", 1, "ab", 2, "xx", 3, 2.1, 10, "abc"); - BOOST_REQUIRE_EQUAL(LIST, term.type()); + BOOST_CHECK_EQUAL(LIST, term.type()); + BOOST_CHECK_EQUAL(2+(6*2), g_alloc_count); for (int j=0; j <= 10; j++) - eterm term = eterm::format(alloc, + eterm term2 = eterm::format(alloc, "{~i, [{~s, ~i}, {~a, ~i}], {~f, ~i}, ~a}", 1, "ab", 2, "xx", 3, 2.1, 10, "abc"); } } - BOOST_REQUIRE_EQUAL(0, my_alloc::count); + BOOST_CHECK_EQUAL(2, g_alloc_count); } -BOOST_AUTO_TEST_CASE( test_pool_format ) +BOOST_AUTO_TEST_CASE( test_refc_pool_format ) { - typedef boost::pool_allocator my_alloc; + using my_alloc = boost::pool_allocator; my_alloc alloc; { for (int i=0; i < 10; i++) { eterm term = eterm::format(alloc, "[~i, [{~s, ~i}, {~a, ~i}], {~f, ~i}, ~a]", 1, "ab", 2, "xx", 3, 2.1, 10, "abc"); - BOOST_REQUIRE_EQUAL(LIST, term.type()); + BOOST_CHECK_EQUAL(LIST, term.type()); for (int j=0; j <= 10; j++) { - eterm term = eterm::format(alloc, + eterm term2 = eterm::format(alloc, "{~i, [{~s, ~i}, {~a, ~i}], {~f, ~i}, ~a}", 1, "ab", 2, "xx", 3, 2.1, 10, "abc"); - BOOST_REQUIRE_EQUAL(TUPLE, term.type()); + BOOST_CHECK_EQUAL(TUPLE, term2.type()); } } } } + +BOOST_AUTO_TEST_CASE( test_refc_list ) +{ + using my_alloc = counted_alloc; + using term = eterm; + my_alloc alloc; + + list lst(nullptr); // Allocates static global empty list + BOOST_CHECK_EQUAL(2, g_alloc_count); + + { + term et = list({1, 2, 3}, alloc); + BOOST_CHECK_EQUAL(4, g_alloc_count); // 1 for blob_t, 1 for blob's data + auto am = et; + BOOST_CHECK_EQUAL(4, g_alloc_count); + } + BOOST_CHECK_EQUAL(2, g_alloc_count); + + { + // Construct a nil list + term et = list(nullptr); + BOOST_CHECK_EQUAL(2, g_alloc_count); + auto am = et; + BOOST_CHECK_EQUAL(2, g_alloc_count); + } + BOOST_CHECK_EQUAL(2, g_alloc_count); +} diff --git a/test/test_mailbox.cpp b/test/test_mailbox.cpp index 50801eb..aadc6f7 100644 --- a/test/test_mailbox.cpp +++ b/test/test_mailbox.cpp @@ -9,23 +9,19 @@ /* ***** BEGIN LICENSE BLOCK ***** -This file is part of the EPI (Erlang Plus Interface) Library. +Copyright 2010 Serge Aleynikov -Copyright (C) 2010 Serge Aleynikov +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. + http://www.apache.org/licenses/LICENSE-2.0 -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. ***** END LICENSE BLOCK ***** */ @@ -40,8 +36,8 @@ BOOST_AUTO_TEST_CASE( test_mailbox ) { boost::asio::io_service io; otp_node node(io, "a"); - atom am_io_server("io_server"); - atom am_main("main"); + const atom am_io_server("io_server"); + const atom am_main("main"); //std::cerr << "mailbox count " << node.registry().count() << std::endl; { @@ -49,8 +45,6 @@ BOOST_AUTO_TEST_CASE( test_mailbox ) otp_mailbox::pointer b(node.create_mailbox(am_main)); //BOOST_REQUIRE_NE(*a.get(), *b.get()); - - otp_mailbox* ag = node.get_mailbox(atom("io_server")); otp_mailbox* bg = node.get_mailbox(atom("main")); @@ -61,7 +55,7 @@ BOOST_AUTO_TEST_CASE( test_mailbox ) BOOST_REQUIRE_EQUAL(*a, *ag); BOOST_REQUIRE_EQUAL(*b, *bg); } - + //node.registry().erase(a.get()); //node.registry().erase(b.get()); } diff --git a/test/test_node.cpp b/test/test_node.cpp index 651677a..a02acc4 100644 --- a/test/test_node.cpp +++ b/test/test_node.cpp @@ -9,27 +9,24 @@ /* ***** BEGIN LICENSE BLOCK ***** -This file is part of the EPI (Erlang Plus Interface) Library. +Copyright 2010 Serge Aleynikov -Copyright (C) 2010 Serge Aleynikov +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. + http://www.apache.org/licenses/LICENSE-2.0 -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. ***** END LICENSE BLOCK ***** */ +#include #include #include "test_alloc.hpp" #include @@ -38,7 +35,7 @@ using namespace eixx; BOOST_AUTO_TEST_CASE( test_node ) { - std::string l_hostname("gbox-car-00"), l_resolved; + std::string l_hostname("localhost"), l_resolved; boost::asio::io_service svc; boost::asio::ip::tcp::resolver resolver(svc); boost::asio::ip::tcp::resolver::query query(l_hostname, ""); diff --git a/test/test_perf.cpp b/test/test_perf.cpp index ee9f974..3c7000f 100644 --- a/test/test_perf.cpp +++ b/test/test_perf.cpp @@ -1,27 +1,44 @@ -#include "test_alloc.hpp" +#include +//#include "test_alloc.hpp" // Uses boost::pool_alloc, which does much worse #include #include #include #include -using namespace EIXX_NAMESPACE; +/// Prevent variable optimization by the compiler +#ifdef _MSC_VER +#pragma optimize("", off) +template void dont_optimize_var(T&& v) { v = v; } +#pragma optimize("", on) +#else +template void dont_optimize_var(T&& v) { asm volatile("":"+r" (v)); } +#endif -int iterations=500000; +using namespace eixx; +int iterations=1000000; +size_t g_size = 0; class timer { struct rusage start, end; void begin() { getrusage(RUSAGE_THREAD, &start); } public: timer() { begin(); } - void sample(const char* title, bool restart = true) { + void restart() { begin(); } + + void sample(const char* title, bool restart = true, size_t out = 0) { getrusage(RUSAGE_THREAD, &end); double diff = (double)(end.ru_utime.tv_sec - start.ru_utime.tv_sec + end.ru_stime.tv_sec - start.ru_stime.tv_sec) + (double)(end.ru_utime.tv_usec - start.ru_utime.tv_usec + end.ru_stime.tv_usec - start.ru_stime.tv_usec)/1000000.0; + g_size += out; - printf("%30s | %.6f (speed: %5.3fus)\n", title, diff, 1000000.0*diff/iterations); + // out is used merely to trick the optimizer + printf("%30s | latency: %5ldns, speed: %9ld/s%s", title, + long(1000000000.0*diff/iterations), + diff > 0 ? long((double)iterations / diff) : 0, + out == 0 ? "\n" : " \n"); if (restart) begin(); } @@ -40,62 +57,160 @@ int main(int argc, char* argv[]) { timer tot; timer t; + size_t size = 0; for (int j=0; j < iterations; j++) - { eterm(1); } - t.sample("Integer"); + { size += eterm(j).encode_size(); } + t.sample("Integer", true, size); for (int j=0; j < iterations; j++) - { eterm(1.0); } - t.sample("Double"); + { size += eterm((double)j).encode_size(); } + t.sample("Double", true, size); for (int j=0; j < iterations; j++) - { eterm(true); } - t.sample("Bool"); + { size += eterm(j&1).encode_size(); } + t.sample("Bool", true, size); for (int j=0; j < iterations; j++) - { eterm("test"); } - t.sample("String"); + { size += eterm("test").encode_size(); } + t.sample("String", true, size); for (int j=0; j < iterations; j++) - { atom("test"); } - t.sample("Atom1"); + { size += atom("test").encode_size(); } + t.sample("Atom1", true, size); { atom a("test"); int k = 0; for (int j=0; j < iterations; j++) - { atom t("test"); if (t==a) k++; } - t.sample("Atom2"); + { atom t1("test"); if (t1==a) k++; size += eterm(t1).encode_size(); } + t.sample("Atom2", true, size); } static const char* ss="This is a test string. This is a test string. This is a test string."; for (int j=0; j < iterations; j++) { - binary b(ss, sizeof(ss)); + binary b(ss, sizeof(ss)); size += eterm(b).encode_size(); } - t.sample("Binary1"); + t.sample("Binary1", true, size); { binary b(ss, sizeof(ss)); for (int j=0; j < iterations; j++) { - eterm t(b); + eterm t1(b); + size += t1.encode_size(); } - t.sample("Binary2"); + t.sample("Binary2", true, size); } eterm l[] = { eterm(10), eterm("atom_value"), eterm(true) }; for (int j=0; j < iterations; j++) - { tuple t(l); } - t.sample("Tuple1"); + { tuple t1(l); size += eterm(t1).encode_size(); } + t.sample("Tuple1", true, size); { tuple tup(l); for (int j=0; j < iterations; j++) - { eterm et(tup); } - t.sample("Tuple2"); + { eterm et(tup); size += et.encode_size(); } + t.sample("Tuple2", true, size); } for (int j=0; j < iterations; j++) - { list t(l); } - t.sample("List1"); + { list t1(l); size += eterm(t1).encode_size(); } + t.sample("List1", true, size); { list ll(l); for (int j=0; j < iterations; j++) - { eterm et(ll); } - t.sample("List2"); + { eterm et(ll); size += et.encode_size(); } + t.sample("List2", true, size); + } + + static const eterm s_md1 = + eterm::format("{md, Xchg, Instr, [{q, [{BPx,BQty}], [{APx, AQty}]}]}"); + static const eterm s_md2 = + eterm::format("{md, Xchg, Instr, [{q, L1, L2}]}"); + static const atom am_Xchg ("Xchg"); + static const atom am_Instr("Instr"); + static const atom am_BPx ("BPx"); + static const atom am_AQty ("AQty"); + static const atom am_APx ("APx"); + static const atom am_BQty ("BQty"); + static const atom am_L1 ("L1"); + static const atom am_L2 ("L2"); + atom xchg ("CNX"); + atom instr("EUR/USD"); + t.restart(); + { + for (int j=0; j < iterations; j++) { + auto x = s_md1.apply({{am_Xchg, xchg}, {am_Instr, instr}, + {am_BPx, 1.2345}, {am_BQty, 100000}, + {am_APx, 1.2355}, {am_AQty, 200000}}); + size += x.encode_size(); + } + t.sample("Apply speed", true, size); + } + { + for (int j=0; j < iterations; j++) { + auto x = s_md2.apply({{am_Xchg, xchg}, {am_Instr, instr}, + {am_L1, list::make(tuple::make(1.2345, 100000))}, + {am_L2, list::make(tuple::make(1.2355, 200000))}}); + size += x.encode_size(); + } + t.sample("Apply/Create speed", true, size); } - tot.sample("Total time"); + atom am_md("md"); + atom am_q ("q"); + { + iterations /= 10; + for (int j=0, e = iterations; j < e; j++) { + auto x = tuple::make(am_md, xchg, instr, + list::make(tuple::make(am_q, + list::make(tuple::make(1.2345, 100000)), + list::make(tuple::make(1.2355, 200000))))); + size += x.encode_size(); + } + t.sample("Nested lists/tuples (1) speed", true, size); + iterations *= 10; + } + + { + iterations /= 10; + for (int j=0, e = iterations; j < e; j++) { + auto x = tuple{am_md, xchg, instr, + list{tuple{am_q, + list{tuple{1.2345, 100000}}, + list{tuple{1.2355, 200000}}}}}; + size += x.encode_size(); + } + t.sample("Nested lists/tuples (2) speed", true, size); + iterations *= 10; + } + + { + static const eterm s_pattern = eterm::format("V"); + static atom am_var("V"); + if (!s_pattern.match(12345)) + std::cerr << "Expected match failed at line " << __LINE__ << std::endl;; + + iterations /= 10; + for (int j=0, e = iterations; j < e; j++) { + varbind binding; + if (s_pattern.match(12345, &binding)) + size++; + } + t.sample("Simple pattern match (1)", true, size); + iterations *= 10; + } + + { + static const eterm s_pattern = eterm::format("{error, [{abc, V}]}"); + static const eterm s_term = eterm::format("{error, [{abc, \"ok\"}]}"); + static atom am_var("V"); + if (!s_term.match(s_pattern)) + std::cerr << "Expected match failed at line " << __LINE__ << std::endl;; + + iterations /= 10; + for (int j=0, e = iterations; j < e; j++) { + varbind binding; + if (s_term.match(s_pattern, &binding)) + size++; + } + t.sample("Nested pattern match (2)", true, size); + iterations *= 10; + } + + if (g_size == 0) + std::cerr << "No iterations performed!" << std::endl; return 0; }