├── .clang-format ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── CMakeLists.txt ├── Doxyfile ├── LICENSE ├── README.md ├── cmake ├── CompilerWarnings.cmake └── Sanitizers.cmake ├── examples ├── CMakeLists.txt ├── advanced.cpp ├── basic.c └── basic.cpp ├── include └── mgclient.h ├── mgclient_cpp ├── CMakeLists.txt └── include │ ├── mgclient-value.hpp │ └── mgclient.hpp ├── src ├── CMakeLists.txt ├── apple │ ├── mgcommon.h │ └── mgsocket.c ├── linux │ ├── mgcommon.h │ └── mgsocket.c ├── mgallocator.c ├── mgallocator.h ├── mgclient.c ├── mgcommon.h ├── mgconstants.h ├── mgmessage.c ├── mgmessage.h ├── mgsession-decoder.c ├── mgsession-encoder.c ├── mgsession.c ├── mgsession.h ├── mgsocket.h ├── mgtransport.c ├── mgtransport.h ├── mgvalue.c ├── mgvalue.h ├── mgwasm.c ├── mgwasm.h └── windows │ ├── mgcommon.h │ └── mgsocket.c ├── tests ├── CMakeLists.txt ├── allocator.cpp ├── bolt-testdata.hpp ├── client.cpp ├── decoder.cpp ├── encoder.cpp ├── gmock_wrapper.h ├── integration │ ├── basic_c.cpp │ └── basic_cpp.cpp ├── test-common.hpp ├── transport.cpp ├── unit │ └── mgclient_value.cpp └── value.cpp ├── tool ├── coverage.sh └── format.sh └── wasm └── install_deps.sh /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | BasedOnStyle: Google 4 | Standard: Cpp11 5 | UseTab: Never 6 | DerivePointerAlignment: false 7 | PointerAlignment: Right 8 | ColumnLimit: 80 9 | IncludeBlocks: Preserve 10 | ... 11 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | pull_request: 6 | workflow_dispatch: 7 | inputs: 8 | apple: 9 | type: boolean 10 | default: false 11 | description: "Build and test on Mac OS" 12 | windows: 13 | type: boolean 14 | default: false 15 | description: "Build and test on Windows (VCPKG)" 16 | windows_mingw: 17 | type: boolean 18 | default: false 19 | description: "Build and test on Windows (MinGW)" 20 | linux: 21 | type: boolean 22 | default: false 23 | description: "Build and test on Linux" 24 | linux_wasm: 25 | type: boolean 26 | default: false 27 | description: "Build and test on Linux (Wasm)" 28 | 29 | jobs: 30 | clang_check: 31 | if: ${{ github.event_name == 'push' || github.event_name == 'pull_request' }} 32 | runs-on: ubuntu-latest 33 | steps: 34 | - name: Set-up repository 35 | uses: actions/checkout@v4 36 | - name: Install environment 37 | run: | 38 | sudo apt install -y clang-format 39 | - name: Run clang formatter 40 | run: | 41 | ./tool/format.sh 42 | 43 | build_and_test_apple: 44 | if: ${{ github.event_name == 'pull_request' || (github.event_name == 'workflow_dispatch' && inputs.apple) }} 45 | strategy: 46 | matrix: 47 | platform: [macos-14, macos-15] 48 | runs-on: ${{ matrix.platform }} 49 | steps: 50 | - name: Set-up repository 51 | uses: actions/checkout@v4 52 | # NOTE: CI can't execute end2end tests because there is no way to run 53 | # Memgraph on CI MacOS machines. 54 | - name: Build and test mgclient 55 | run: | 56 | mkdir build 57 | cd build 58 | cmake -DOPENSSL_ROOT_DIR="$(ls -rd -- /usr/local/Cellar/openssl@1.1/* | head -n 1)" -DCMAKE_INSTALL_PREFIX=/usr/local -DBUILD_TESTING=ON -DBUILD_TESTING_INTEGRATION=ON -DC_WARNINGS_AS_ERRORS=ON -DCPP_WARNINGS_AS_ERRORS=ON .. 59 | cmake --build . --parallel 60 | ctest -E "example|integration" 61 | sudo make install 62 | 63 | build_windows_compiler: 64 | if: ${{ github.event_name == 'pull_request' || (github.event_name == 'workflow_dispatch' && inputs.windows) }} 65 | strategy: 66 | fail-fast: false 67 | matrix: 68 | include: 69 | - os: windows-2022 70 | - os: windows-2025 71 | runs-on: ${{ matrix.os }} 72 | env: 73 | VCPKG_ROOT: "${{ github.workspace }}\\vcpkg" 74 | deps: "openssl:x64-windows" 75 | steps: 76 | - name: Set-up repository 77 | uses: actions/checkout@v4 78 | - name: Restore vcpkg and its artifacts 79 | uses: actions/cache@v4 80 | id: vcpkg-cache 81 | with: 82 | path: ${{ env.VCPKG_ROOT }} 83 | key: ${{ matrix.os }}-${{ env.deps }} 84 | - name: Get vcpkg 85 | if: ${{ steps.vcpkg-cache.outputs.cache-hit != 'true' }} 86 | run: | 87 | cd ${{ github.workspace }} 88 | git clone https://github.com/Microsoft/vcpkg.git 89 | cd vcpkg 90 | .\bootstrap-vcpkg.bat 91 | - name: Remove system vcpkg 92 | run: rm -rf "$VCPKG_INSTALLATION_ROOT" 93 | shell: bash 94 | - name: Install vcpkg packages 95 | run: | 96 | ${{ env.VCPKG_ROOT }}\vcpkg.exe install ${{ env.deps }} 97 | - name: Build and test mgclient 98 | run: | 99 | mkdir build 100 | cd build 101 | cmake -DOPENSSL_ROOT_DIR="${{ env.VCPKG_ROOT }}\installed\x64-windows" .. 102 | cmake --build . 103 | 104 | build_and_test_linux: 105 | if: ${{ github.event_name == 'pull_request' || (github.event_name == 'workflow_dispatch' && inputs.linux) }} 106 | strategy: 107 | matrix: 108 | platform: [ubuntu-24.04, fedora-41] 109 | mgversion: ["latest"] 110 | packages: ["gcc g++ clang cmake git"] 111 | gcc-postfix: [""] 112 | clang-postfix: [""] 113 | runs-on: ["self-hosted", "X64"] 114 | env: 115 | MEMGRAPH_NETWORK: "host" 116 | steps: 117 | - name: Set-up repository 118 | uses: actions/checkout@v4 119 | 120 | - name: Log in to Docker Hub 121 | uses: docker/login-action@v3 122 | with: 123 | username: ${{ secrets.DOCKERHUB_USERNAME }} 124 | password: ${{ secrets.DOCKERHUB_TOKEN }} 125 | 126 | - name: Launch Docker Container 127 | run: | 128 | docker network create ${{ env.MEMGRAPH_NETWORK }} || true 129 | platform="${{ matrix.platform }}" 130 | tag=${platform//-/:} 131 | docker run -d --rm --name testcontainer --network ${{ env.MEMGRAPH_NETWORK }} "$tag" sleep infinity 132 | 133 | - name: Install environment 134 | run: | 135 | if [[ "${{ matrix.platform }}" == ubuntu* ]]; then 136 | docker exec -i testcontainer bash -c "apt update && apt install -y ${{ matrix.packages }} libssl-dev" 137 | else 138 | docker exec -i testcontainer bash -c "dnf install -y ${{ matrix.packages }} openssl-devel" 139 | fi 140 | 141 | - name: Copy Repo Into Container 142 | run: | 143 | docker cp . testcontainer:/mgclient 144 | 145 | - name: Set Memgraph Version 146 | run: | 147 | if [[ "${{ matrix.mgversion }}" == "latest" ]]; then 148 | mgversion=$(curl -s https://api.github.com/repos/memgraph/memgraph/releases/latest | jq -r .tag_name) 149 | mgversion=${mgversion#v} 150 | else 151 | mgversion="${{ matrix.mgversion }}" 152 | fi 153 | echo "MGVERSION=$mgversion" >> $GITHUB_ENV 154 | 155 | - name: Download Memgraph Docker image 156 | run: | 157 | curl -L https://download.memgraph.com/memgraph/v${{ env.MGVERSION }}/docker/memgraph-${{ env.MGVERSION }}-docker.tar.gz > memgraph-docker.tar.gz 158 | 159 | - name: Load and run Memgraph Docker image 160 | run: | 161 | docker load -i memgraph-docker.tar.gz 162 | docker run -d --rm --name memgraphcontainer --network ${{ env.MEMGRAPH_NETWORK }} -p 7687:7687 memgraph/memgraph --telemetry-enabled=false 163 | rm memgraph-docker.tar.gz 164 | sleep 5 165 | docker logs memgraphcontainer 166 | 167 | - name: Build with gcc, test and install mgclient 168 | run: | 169 | cmake_configure="cmake \ 170 | -DCMAKE_C_COMPILER=gcc${{ matrix.gcc-postfix }} \ 171 | -DCMAKE_CXX_COMPILER=g++${{ matrix.gcc-postfix }} \ 172 | -DCMAKE_BUILD_TYPE=Release \ 173 | -DBUILD_TESTING=ON \ 174 | -DBUILD_TESTING_INTEGRATION=ON \ 175 | -DC_WARNINGS_AS_ERRORS=ON \ 176 | -DCPP_WARNINGS_AS_ERRORS=ON \ 177 | .." 178 | 179 | docker exec -i testcontainer bash -c " 180 | mkdir /mgclient/build-gcc && 181 | cd /mgclient/build-gcc && 182 | $cmake_configure && 183 | cmake --build . --parallel && 184 | ctest --output-on-failure && 185 | make install" 186 | 187 | - name: Build with clang, test and install mgclient 188 | run: | 189 | cmake_configure="cmake \ 190 | -DCMAKE_C_COMPILER=clang${{ matrix.clang-postfix }} \ 191 | -DCMAKE_CXX_COMPILER=clang++${{ matrix.clang-postfix }} \ 192 | -DCMAKE_BUILD_TYPE=Release \ 193 | -DBUILD_TESTING=ON \ 194 | -DBUILD_TESTING_INTEGRATION=ON \ 195 | -DC_WARNINGS_AS_ERRORS=ON \ 196 | -DCPP_WARNINGS_AS_ERRORS=ON \ 197 | .." 198 | 199 | docker exec -i testcontainer bash -c " 200 | mkdir /mgclient/build-clang && 201 | cd /mgclient/build-clang && 202 | $cmake_configure && 203 | cmake --build . --parallel && 204 | ctest --output-on-failure && 205 | make install" 206 | 207 | - name: Cleanup 208 | if: always() 209 | run: | 210 | docker stop testcontainer || echo "testcontainer already stopped" 211 | docker stop memgraphcontainer || echo "memgraphcontainer already stopped" 212 | docker wait testcontainer || true 213 | docker wait memgraphcontainer || true 214 | docker rm "${{ env.MEMGRAPH_NETWORK }}" || echo "network already removed" 215 | docker rmi "${{ matrix.platform }}" || echo "${{ matrix.platform }} image not found" 216 | docker rmi memgraph/memgraph || echo "memgraph/memgraph image not found" 217 | 218 | 219 | 220 | # GitHub actions can't run Linux Docker container on Windows machine 221 | # https://github.com/actions/virtual-environments/issues/1143. 222 | # 223 | # The only option to test this project on GitHub Actions under Windows is to 224 | # run Memgraph under [WSL](https://docs.microsoft.com/en-us/windows/wsl/). 225 | # Memgraph has to be started manually because systemd is not available on 226 | # WSL (init process does not exist). 227 | # This particular test might be flaky (hence the additional memgraph process check) 228 | build_and_test_windows_mingw: 229 | if: ${{ github.event_name == 'pull_request' || (github.event_name == 'workflow_dispatch' && inputs.windows_mingw) }} 230 | strategy: 231 | fail-fast: false 232 | matrix: 233 | os: [windows-2022, windows-2025] 234 | msystem: [MINGW64] 235 | arch: [x86_64] 236 | mgversion: ["latest"] 237 | runs-on: ${{ matrix.os }} 238 | defaults: 239 | run: 240 | shell: msys2 {0} 241 | steps: 242 | - name: Set-up repository 243 | uses: actions/checkout@v4 244 | - uses: msys2/setup-msys2@v2 245 | with: 246 | msystem: ${{ matrix.msystem }} 247 | update: true 248 | install: git base-devel mingw-w64-${{ matrix.arch }}-toolchain mingw-w64-${{ matrix.arch }}-cmake mingw-w64-${{ matrix.arch }}-openssl 249 | - uses: Vampire/setup-wsl@v5 250 | with: 251 | distribution: Ubuntu-24.04 252 | - name: Set Memgraph Version 253 | run: | 254 | if [[ "${{ matrix.mgversion }}" == "latest" ]]; then 255 | mgversion=$( 256 | curl -s https://api.github.com/repos/memgraph/memgraph/releases/latest \ 257 | | grep -m1 '"tag_name":' \ 258 | | sed -E 's/.*"([^"]+)".*/\1/' \ 259 | | sed 's/^v//' 260 | ) 261 | else 262 | mgversion="${{ matrix.mgversion }}" 263 | fi 264 | echo "MGVERSION=$mgversion" >> $GITHUB_ENV 265 | - name: Download, install and run Memgraph under WSL 266 | shell: wsl-bash {0} # root shell 267 | run: | 268 | mkdir ~/memgraph 269 | curl -L https://download.memgraph.com/memgraph/v${{ env.MGVERSION }}/ubuntu-24.04/memgraph_${{ env.MGVERSION }}-1_amd64.deb --output ~/memgraph/memgraph.deb 270 | dpkg -i ~/memgraph/memgraph.deb 271 | nohup /usr/lib/memgraph/memgraph --bolt-port 7687 --bolt-cert-file="" --bolt-key-file="" --data-directory="~/memgraph/data" --storage-properties-on-edges=true --storage-snapshot-interval-sec=0 --storage-wal-enabled=false --data-recovery-on-startup=false --storage-snapshot-on-exit=false --telemetry-enabled=false --log-file='' & 272 | sleep 1 # Wait for Memgraph a bit. 273 | - name: Build and test mgclient 274 | run: | 275 | mkdir build 276 | cd build 277 | cmake .. -G "MinGW Makefiles" -DBUILD_TESTING=ON -DBUILD_TESTING_INTEGRATION=ON -DC_WARNINGS_AS_ERRORS=ON -DCPP_WARNINGS_AS_ERRORS=ON -DCMAKE_POLICY_VERSION_MINIMUM=3.5 278 | cmake --build . --parallel 279 | 280 | - name: Verify Memgraph is running under WSL 281 | shell: wsl-bash {0} 282 | run: | 283 | # process 284 | if ! pgrep -x memgraph; then 285 | echo "❌ Memgraph not running" >&2 286 | exit 1 287 | fi 288 | echo "✅ Memgraph check passed" 289 | 290 | - name: Run Tests 291 | run: | 292 | ctest --verbose -R "allocator|value|example_basic|integration_basic" 293 | 294 | build_and_test_linux_wasm: 295 | if: ${{ github.event_name == 'pull_request' || (github.event_name == 'workflow_dispatch' && inputs.linux_wasm) }} 296 | strategy: 297 | matrix: 298 | platform: [ubuntu-24.04] 299 | runs-on: ${{ matrix.platform }} 300 | steps: 301 | - name: Set-up repository 302 | uses: actions/checkout@v4 303 | 304 | - name: Build with clang 305 | run: | 306 | mkdir build 307 | cd build 308 | cmake .. -DCMAKE_BUILD_TYPE=Release -DC_WARNINGS_AS_ERRORS=ON -DWASM=ON 309 | make 310 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Build directory. 2 | build/ 3 | 4 | # coverage generated report directory 5 | tool/generated/ 6 | 7 | # vim temporary files 8 | *.swp 9 | *.swo 10 | 11 | # .vscode project files. 12 | .vscode/ 13 | 14 | # Doxygen output files. 15 | html/ 16 | 17 | # emsdk folder pulled from wasm/install_deps.sh 18 | wasm/emsdk 19 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016-2020 Memgraph Ltd. [https://memgraph.com] 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | cmake_minimum_required(VERSION 3.8) 16 | 17 | if(WASM) 18 | execute_process(COMMAND ${CMAKE_SOURCE_DIR}/wasm/install_deps.sh) 19 | set(CMAKE_TOOLCHAIN_FILE "${CMAKE_SOURCE_DIR}/wasm/emsdk/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake") 20 | endif() 21 | 22 | project(mgclient VERSION 1.4.5) 23 | # Minor version increase can also mean ABI incompatibility with previous 24 | # versions. IMPORTANT: Take care of the SO version manually. 25 | set(mgclient_SOVERSION 2) 26 | set(WASM OFF CACHE BOOL "Compile mgclient for wasm") 27 | add_definitions(-DMGCLIENT_VERSION="${mgclient_VERSION}") 28 | 29 | # Deal with the operating system. 30 | if((NOT UNIX) AND EMSCRIPTEN) 31 | message(FATAL_ERROR "WASM build is only supported in Linux") 32 | endif() 33 | 34 | if(WIN32) 35 | message(STATUS "ON WINDOWS BUILD") 36 | set(MGCLIENT_ON_WINDOWS TRUE) 37 | add_definitions(-DMGCLIENT_ON_WINDOWS) 38 | # CMAKE_FIND_LIBRARY_PREFIXES on Windows is "lib;". 39 | # "lib;" breaks gtest lib referencing. 40 | set(MGCLIENT_FIND_LIBRARY_PREFIXES "lib") 41 | elseif(UNIX AND NOT APPLE) 42 | if(EMSCRIPTEN) 43 | message(STATUS "ON LINUX WASM BUILD") 44 | elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux") 45 | message(STATUS "ON LINUX BUILD") 46 | else() 47 | message(FATAL_ERROR "Unsupported operating system. Please create issue or contribute!") 48 | endif() 49 | set(MGCLIENT_ON_LINUX TRUE) 50 | add_definitions(-DMGCLIENT_ON_LINUX) 51 | set(MGCLIENT_FIND_LIBRARY_PREFIXES "${CMAKE_FIND_LIBRARY_PREFIXES}") 52 | elseif(APPLE) 53 | message(STATUS "ON APPLE BUILD") 54 | set(MGCLIENT_ON_APPLE TRUE) 55 | add_definitions(-DMGCLIENT_ON_APPLE) 56 | set(MGCLIENT_FIND_LIBRARY_PREFIXES "${CMAKE_FIND_LIBRARY_PREFIXES}") 57 | else() 58 | message(FATAL_ERROR "Unsupported operating system. Please create issue or contribute!") 59 | endif() 60 | 61 | # Building tests is disabled by default to simplify the default build config. 62 | option(BUILD_TESTING "" OFF) 63 | message(STATUS "BUILD_TESTING: ${BUILD_TESTING}") 64 | # Integration tests are disabled by default, since the memgraph instance is 65 | # neccessary to execute them. 66 | option(BUILD_TESTING_INTEGRATION "" OFF) 67 | message(STATUS "BUILD_TESTING_INTEGRATION: ${BUILD_TESTING_INTEGRATION}") 68 | 69 | # build header only cpp bindings 70 | option(BUILD_CPP_BINDINGS "" OFF) 71 | if (BUILD_TESTING OR BUILD_TESTING_INTEGRATION) 72 | set(BUILD_CPP_BINDINGS ON) 73 | message(STATUS "Testing triggering cpp binding dependancy.") 74 | endif() 75 | 76 | message(STATUS "BUILD_CPP_BINDINGS: ${BUILD_CPP_BINDINGS}") 77 | 78 | include(CTest) 79 | 80 | set(CMAKE_C_STANDARD 11) 81 | # C++17 is fine here because it is required only for the testing purposes. 82 | set(CMAKE_CXX_STANDARD 17) 83 | set(C_STANDARD_REQUIRED ON) 84 | set(CXX_STANDARD_REQUIRED ON) 85 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 86 | 87 | # Link these "libraries" to set compiler warnings on targets. 88 | include(cmake/CompilerWarnings.cmake) 89 | add_library(project_c_warnings INTERFACE) 90 | set_project_c_warnings(project_c_warnings) 91 | add_library(project_cpp_warnings INTERFACE) 92 | set_project_cpp_warnings(project_cpp_warnings) 93 | 94 | if(EMSCRIPTEN) 95 | # same as project_c_warnings but without -O3. In the future we should 96 | # experiment and switch to O3. because it reduces the js output size 97 | # significantly. 98 | set(CMAKE_C_FLAGS_DEBUG "-g -O0 -Wall -Wextra -Wpedantic -std=gnu11") 99 | set(CMAKE_C_FLAGS_RELEASE "-O2 -DNDEBUG -Wall -Wextra -Wpedantic -std=gnu11") 100 | endif() 101 | 102 | # Set default build type to 'Release' 103 | if(NOT CMAKE_BUILD_TYPE) 104 | set(CMAKE_BUILD_TYPE "Release") 105 | endif() 106 | message(STATUS "CMake build type: ${CMAKE_BUILD_TYPE}") 107 | 108 | # Set default instalation directory to '/usr' 109 | if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) 110 | # '/usr' is a special case, for more details see: 111 | # https://cmake.org/cmake/help/latest/module/GNUInstallDirs.html#special-cases 112 | set(CMAKE_INSTALL_PREFIX "/usr") 113 | endif() 114 | 115 | add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/src) 116 | 117 | if(BUILD_CPP_BINDINGS) 118 | add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/mgclient_cpp) 119 | endif() 120 | 121 | if(BUILD_TESTING) 122 | add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/tests) 123 | endif() 124 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Actions Status](https://github.com/memgraph/mgclient/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/memgraph/mgclient/actions) 2 | 3 | # mgclient 4 | 5 | mgclient is a C library interface for [Memgraph](https://www.memgraph.com) 6 | database. 7 | 8 | ## Building and installing on Apple 9 | 10 | To build and install mgclient from source you will need: 11 | - CMake version >= 3.8 12 | - OpenSSL version >= 1.0.2 13 | - Apple LLVM/clang >= 8.0.0 14 | 15 | Once everything is in place, create a build directory inside the project 16 | root directory and configure the build by running CMake from it: 17 | 18 | ``` 19 | mkdir build 20 | cd build 21 | cmake .. 22 | ``` 23 | 24 | NOTE: Dealing with OpenSSL might be a bit tricky. If OpenSSL is not available 25 | on the system, please use, e.g., [brew](https://brew.sh/) package manager to 26 | install OpenSSL with the following command: 27 | 28 | ``` 29 | brew install openssl@1.1 30 | ``` 31 | 32 | If `cmake` can't locate OpenSSL, please set `OPENSSL_ROOT_DIR` to a valid path. 33 | Examples follow: 34 | 35 | ``` 36 | # M1 with brew installed 37 | cmake -DOPENSSL_ROOT_DIR="$(brew --prefix openssl)" .. 38 | 39 | # Using only ls command 40 | cmake -DOPENSSL_ROOT_DIR="$(ls -rd -- /usr/local/Cellar/openssl@1.1/* | head -n 1)" .. 41 | ``` 42 | 43 | After running CMake, you should see a Makefile in the build directory. Then you 44 | can build the project by running: 45 | 46 | ``` 47 | make 48 | ``` 49 | 50 | This will build two `mgclient` library flavours: a static library (named 51 | `libmgclient.a`) and a shared library (named `libmgclient.dylib`). 52 | 53 | To install the libraries and corresponding header files run: 54 | 55 | ``` 56 | make install 57 | ``` 58 | 59 | This will install to system default installation directory. If you want to 60 | change this location, use `-DCMAKE_INSTALL_PREFIX` option when running CMake. 61 | 62 | ## Building and installing on Linux 63 | 64 | To build and install mgclient from source you will need: 65 | - CMake version >= 3.8 66 | - OpenSSL version >= 1.0.2 67 | - gcc >= 8 or clang >= 8 68 | 69 | To install minimum compile dependencies on Debian / Ubuntu: 70 | 71 | ``` 72 | apt-get install -y git cmake make gcc g++ libssl-dev 73 | ``` 74 | 75 | On RedHat / CentOS / Fedora: 76 | 77 | ``` 78 | yum install -y git cmake make gcc gcc-c++ openssl-devel 79 | ``` 80 | 81 | Once everything is in place, create a build directory inside the source 82 | directory and configure the build by running CMake from it: 83 | 84 | ``` 85 | mkdir build 86 | cd build 87 | cmake .. 88 | ``` 89 | 90 | After running CMake, you should see a Makefile in the build directory. Then you 91 | can build the project by running: 92 | 93 | ``` 94 | make 95 | ``` 96 | 97 | This will build two `mgclient` library flavours: a static library (usually 98 | named `libmgclient.a`) and a shared library (usually named `libmgclient.so`). 99 | 100 | To install the libraries and corresponding header files run: 101 | 102 | ``` 103 | make install 104 | ``` 105 | 106 | This will install to system default installation directory. If you want to 107 | change this location, use `-DCMAKE_INSTALL_PREFIX` option when running CMake. 108 | 109 | If you want to build and run tests, in the build directory run: 110 | 111 | ``` 112 | cmake -DBUILD_TESTING=ON -DBUILD_TESTING_INTEGRATION=ON .. 113 | ctest 114 | ``` 115 | 116 | ## Building and installing on Windows 117 | 118 | To build and install mgclient from source on Windows you will need: 119 | - CMake version >= 3.8 120 | - OpenSSL version >= 1.0.2 121 | - MinGW: gcc >= 8 or Windows Compiler (take a look 122 | [here](https://blog.knatten.org/2022/08/26/microsoft-c-versions-explained/) 123 | to understand versioning in a bit more details): 124 | VS >= 17 2022, MSVC >= 14.34, C >= 19.34 125 | 126 | ### Windows Compiler 127 | 128 | ``` 129 | mkdir build 130 | cd build 131 | cmake .. 132 | cmake --build . 133 | ``` 134 | 135 | Depending on where OpenSSL is installed you might need to define 136 | `OPENSSL_ROOT_DIR`, example follows: 137 | ``` 138 | cmake -DOPENSSL_ROOT_DIR="$VCPKG_ROOT\installed\x64-windows" .. 139 | ``` 140 | 141 | To install OpenSSL [vcpkg](https://vcpkg.io/en/index.html) can be used: 142 | ``` 143 | vcpkg install openssl:x64-windows 144 | ``` 145 | or you can download and install OpenSSL from 146 | [here](https://slproweb.com/products/Win32OpenSSL.html). 147 | 148 | ### MinGW 149 | 150 | - Install MSYS2 from https://www.msys2.org/. 151 | - Install MinGW toolchain with the following command: 152 | ``` 153 | pacman -S --needed git base-devel mingw-w64-i686-toolchain mingw-w64-x86_64-toolchain mingw-w64-i686-cmake mingw-w64-x86_64-cmake mingw-w64-i686-openssl mingw-w64-x86_64-openssl 154 | ``` 155 | 156 | Once the environment is ready, please run: 157 | 158 | ``` 159 | mkdir build 160 | cd build 161 | cmake .. -G "MinGW Makefiles" 162 | cmake --build . --target install 163 | ``` 164 | 165 | ## Building WASM (Linux only) 166 | 167 | Compiling `mgclient` for wasm requires the Emscripten sdk. This is automated in 168 | the following steps: 169 | 1. mkdir build && cd build 170 | 2. cmake .. -DWASM=ON 171 | 3. make 172 | 173 | Now there should be an `mgclient.js` and an `mgclient.wasm` found in 174 | `mgclient/build/` 175 | 176 | ## Using the library 177 | 178 | The library provides header files located under the include folder. All library 179 | functionality is documented in these files in Doxygen format. You can also 180 | build HTML version of the documentation by running `doxygen` command from 181 | project root directory. 182 | 183 | ## Examples 184 | 185 | All the examples of the usage of the mgclient are contained in the 186 | [examples](examples) folder, including the C++ wrapper. 187 | 188 | An example on how to include mgclient inside a CMake project is located under 189 | `examples/CMakeLists.txt`. 190 | -------------------------------------------------------------------------------- /cmake/CompilerWarnings.cmake: -------------------------------------------------------------------------------- 1 | function(set_project_c_warnings library_name) 2 | option(C_WARNINGS_AS_ERRORS "Treat C compiler warnings as errors" OFF) 3 | 4 | set(CLANG_C_WARNINGS 5 | -Wall 6 | -Wextra 7 | -Wpedantic 8 | ) 9 | set(GCC_C_WARNINGS 10 | -Wall 11 | -Wextra 12 | -Wpedantic 13 | ) 14 | 15 | if (C_WARNINGS_AS_ERRORS) 16 | set(CLANG_C_WARNINGS ${CLANG_C_WARNINGS} -Werror) 17 | set(GCC_C_WARNINGS ${GCC_C_WARNINGS} -Werror) 18 | endif() 19 | 20 | if(CMAKE_C_COMPILER_ID STREQUAL "Clang") 21 | set(C_PROJECT_WARNINGS ${CLANG_C_WARNINGS}) 22 | elseif(CMAKE_C_COMPILER_ID STREQUAL "AppleClang") 23 | set(C_PROJECT_WARNINGS ${CLANG_C_WARNINGS}) 24 | elseif(CMAKE_C_COMPILER_ID STREQUAL "GNU") 25 | set(C_PROJECT_WARNINGS ${GCC_C_WARNINGS}) 26 | endif() 27 | 28 | target_compile_options(${library_name} INTERFACE ${C_PROJECT_WARNINGS}) 29 | endfunction() 30 | 31 | function(set_project_cpp_warnings library_name) 32 | option(CPP_WARNINGS_AS_ERRORS "Treat CPP compiler warnings as errors" OFF) 33 | 34 | set(CLANG_CPP_WARNINGS 35 | -Wall 36 | -Wextra 37 | -Wpedantic 38 | -Wno-nested-anon-types 39 | ) 40 | set(GCC_CPP_WARNINGS 41 | -Wall 42 | -Wextra 43 | -Wpedantic 44 | -Wno-maybe-uninitialized 45 | ) 46 | 47 | if (CPP_WARNINGS_AS_ERRORS) 48 | set(CLANG_CPP_WARNINGS ${CLANG_CPP_WARNINGS} -Werror) 49 | set(GCC_CPP_WARNINGS ${GCC_CPP_WARNINGS} -Werror) 50 | endif() 51 | 52 | if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang") 53 | set(CPP_PROJECT_WARNINGS ${CLANG_CPP_WARNINGS}) 54 | elseif(CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang") 55 | set(CPP_PROJECT_WARNINGS ${CLANG_CPP_WARNINGS}) 56 | elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") 57 | set(CPP_PROJECT_WARNINGS ${GCC_CPP_WARNINGS}) 58 | endif() 59 | 60 | target_compile_options(${library_name} INTERFACE ${CPP_PROJECT_WARNINGS}) 61 | endfunction() 62 | -------------------------------------------------------------------------------- /cmake/Sanitizers.cmake: -------------------------------------------------------------------------------- 1 | function(enable_sanitizers project_name) 2 | if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang") 3 | option(ENABLE_COVERAGE "Enable coverage report for clang" OFF) 4 | message(STATUS "ENABLE_COVERAGE: ${ENABLE_COVERAGE}") 5 | if (ENABLE_COVERAGE) 6 | target_compile_options(${project_name} INTERFACE -fprofile-instr-generate -fcoverage-mapping -O0 -g) 7 | target_link_libraries(${project_name} INTERFACE -fprofile-instr-generate -fcoverage-mapping) 8 | endif() 9 | endif() 10 | endfunction() 11 | -------------------------------------------------------------------------------- /examples/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.8) 2 | 3 | project(example VERSION 0.1) 4 | 5 | include(ExternalProject) 6 | 7 | set(MGCLIENT_GIT_TAG "v1.4.0" CACHE STRING "mgclient git tag") 8 | set(MGCLIENT_LIBRARY mgclient-lib) 9 | set(MGCLIENT_INSTALL_DIR ${CMAKE_BINARY_DIR}/mgclient) 10 | set(MGCLIENT_INCLUDE_DIRS ${MGCLIENT_INSTALL_DIR}/include) 11 | if (UNIX AND NOT APPLE) 12 | set(MGCLIENT_LIBRARY_PATH ${MGCLIENT_INSTALL_DIR}/lib/libmgclient.so) 13 | elseif (WIN32) 14 | set(MGCLIENT_LIBRARY_PATH ${MGCLIENT_INSTALL_DIR}/lib/mgclient.dll) 15 | endif() 16 | ExternalProject_Add(mgclient-proj 17 | PREFIX mgclient-proj 18 | GIT_REPOSITORY https://github.com/memgraph/mgclient.git 19 | GIT_TAG "${MGCLIENT_GIT_TAG}" 20 | CMAKE_ARGS "-DCMAKE_INSTALL_PREFIX=${MGCLIENT_INSTALL_DIR}" 21 | "-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}" 22 | "-DBUILD_CPP_BINDINGS=ON" 23 | "-DCMAKE_POSITION_INDEPENDENT_CODE=ON" 24 | BUILD_BYPRODUCTS "${MGCLIENT_LIBRARY_PATH}" 25 | INSTALL_DIR "${PROJECT_BINARY_DIR}/mgclient" 26 | ) 27 | add_library(${MGCLIENT_LIBRARY} SHARED IMPORTED) 28 | target_compile_definitions(${MGCLIENT_LIBRARY} INTERFACE mgclient_shared_EXPORTS) 29 | set_property(TARGET ${MGCLIENT_LIBRARY} PROPERTY IMPORTED_LOCATION ${MGCLIENT_LIBRARY_PATH}) 30 | if (WIN32) 31 | set_property(TARGET ${MGCLIENT_LIBRARY} PROPERTY IMPORTED_IMPLIB ${MGCLIENT_INSTALL_DIR}/lib/mgclient.lib) 32 | endif() 33 | add_dependencies(${MGCLIENT_LIBRARY} mgclient-proj) 34 | -------------------------------------------------------------------------------- /examples/advanced.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "mgclient.hpp" 5 | 6 | void ClearDatabaseData(mg::Client *client) { 7 | if (!client->Execute("MATCH (n) DETACH DELETE n;")) { 8 | std::cerr << "Failed to delete all data from the database." << std::endl; 9 | std::exit(1); 10 | } 11 | client->DiscardAll(); 12 | } 13 | 14 | std::string MgValueToString(const mg::ConstValue &value) { 15 | std::string value_str = ""; 16 | if (value.type() == mg::Value::Type::Int) { 17 | value_str = std::to_string(value.ValueInt()); 18 | } else if (value.type() == mg::Value::Type::String) { 19 | value_str = value.ValueString(); 20 | } else if (value.type() == mg::Value::Type::Bool) { 21 | value_str = std::to_string(value.ValueBool()); 22 | } else if (value.type() == mg::Value::Type::Double) { 23 | value_str = std::to_string(value.ValueDouble()); 24 | } else if (value.type() == mg::Value::Type::Point2d) { 25 | auto point2d = value.ValuePoint2d(); 26 | value_str += "Point2D({ srid:" + std::to_string(point2d.srid()) + 27 | ", x:" + std::to_string(point2d.x()) + 28 | ", y:" + std::to_string(point2d.y()) + " })"; 29 | } else if (value.type() == mg::Value::Type::Point3d) { 30 | auto point3d = value.ValuePoint3d(); 31 | value_str += "Point3D({ srid:" + std::to_string(point3d.srid()) + 32 | ", x:" + std::to_string(point3d.x()) + 33 | ", y:" + std::to_string(point3d.y()) + 34 | ", z:" + std::to_string(point3d.z()) + " })"; 35 | } else if (value.type() == mg::Value::Type::List) { 36 | value_str += "["; 37 | for (auto item : value.ValueList()) { 38 | value_str += MgValueToString(item) + ","; 39 | } 40 | value_str += "]"; 41 | } else { 42 | std::cerr << "Uncovered converstion from data type to a string" 43 | << std::endl; 44 | std::exit(1); 45 | } 46 | return value_str; 47 | } 48 | 49 | int main(int argc, char *argv[]) { 50 | if (argc != 3) { 51 | std::cerr << "Usage: " << argv[0] << " [host] [port]\n"; 52 | std::exit(1); 53 | } 54 | 55 | mg::Client::Init(); 56 | 57 | { 58 | mg::Client::Params params; 59 | params.host = argv[1]; 60 | params.port = static_cast(atoi(argv[2])); 61 | auto client = mg::Client::Connect(params); 62 | if (!client) { 63 | std::cerr << "Failed to connect." << std::endl; 64 | return 1; 65 | } 66 | 67 | ClearDatabaseData(client.get()); 68 | 69 | if (!client->Execute("CREATE INDEX ON :Person(id);")) { 70 | std::cerr << "Failed to create an index." << std::endl; 71 | return 1; 72 | } 73 | client->DiscardAll(); 74 | 75 | if (!client->Execute( 76 | "CREATE (:Person:Entrepreneur {id: 0, age: 40, name: 'John', " 77 | "isStudent: false, score: 5.0, " 78 | "position2D: point({x: 1, y: 2, srid: 4326}), " 79 | "position3D: point({x: 8, y: 9, z: 10, srid: 9757}) });")) { 80 | std::cerr << "Failed to add data." << std::endl; 81 | return 1; 82 | } 83 | client->DiscardAll(); 84 | 85 | if (!client->Execute("MATCH (n) RETURN n;")) { 86 | std::cerr << "Failed to read data." << std::endl; 87 | return 1; 88 | } 89 | if (const auto maybe_data = client->FetchAll()) { 90 | const auto data = *maybe_data; 91 | std::cout << "Number of results: " << data.size() << std::endl; 92 | } 93 | 94 | mg::Map query_params(2); 95 | query_params.Insert("id", mg::Value(0)); 96 | mg::List list_param(std::vector{mg::Value(1), mg::Value(1)}); 97 | query_params.Insert("list", mg::Value(std::move(list_param))); 98 | if (!client->Execute("CREATE (n {id: $id, list: $list}) RETURN n;", 99 | query_params.AsConstMap())) { 100 | std::cerr << "Failed to read data by parametrized query." << std::endl; 101 | return 1; 102 | } 103 | if (const auto maybe_data = client->FetchAll()) { 104 | const auto data = *maybe_data; 105 | std::cout << "Number of results: " << data.size() << std::endl; 106 | } 107 | 108 | if (!client->Execute("MATCH (n) RETURN n;")) { 109 | std::cerr << "Failed to read data." << std::endl; 110 | return 1; 111 | } 112 | while (const auto maybe_result = client->FetchOne()) { 113 | const auto result = *maybe_result; 114 | if (result.size() < 1) { 115 | continue; 116 | } 117 | const auto value = result[0]; 118 | if (value.type() == mg::Value::Type::Node) { 119 | const auto node = value.ValueNode(); 120 | auto labels = node.labels(); 121 | std::string labels_str = std::accumulate( 122 | labels.begin(), labels.end(), std::string(""), 123 | [](const std::string &acc, const std::string_view value) { 124 | return acc + ":" + std::string(value); 125 | }); 126 | const auto props = node.properties(); 127 | std::string props_str = 128 | std::accumulate(props.begin(), props.end(), std::string("{"), 129 | [](const std::string &acc, const auto &key_value) { 130 | const auto &[key, value] = key_value; 131 | return acc + " " + std::string(key) + ": " + 132 | MgValueToString(value); 133 | }) + 134 | " }"; 135 | std::cout << labels_str << " " << props_str << std::endl; 136 | } 137 | } 138 | 139 | ClearDatabaseData(client.get()); 140 | } 141 | 142 | mg::Client::Finalize(); 143 | 144 | return 0; 145 | } 146 | -------------------------------------------------------------------------------- /examples/basic.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | int main(int argc, char *argv[]) { 7 | if (argc != 4) { 8 | fprintf(stderr, "Usage: %s [host] [port] [query]\n", argv[0]); 9 | exit(1); 10 | } 11 | 12 | mg_init(); 13 | printf("mgclient version: %s\n", mg_client_version()); 14 | 15 | mg_session_params *params = mg_session_params_make(); 16 | if (!params) { 17 | fprintf(stderr, "failed to allocate session parameters\n"); 18 | exit(1); 19 | } 20 | mg_session_params_set_host(params, argv[1]); 21 | mg_session_params_set_port(params, (uint16_t)atoi(argv[2])); 22 | mg_session_params_set_sslmode(params, MG_SSLMODE_DISABLE); 23 | 24 | mg_session *session = NULL; 25 | int status = mg_connect(params, &session); 26 | mg_session_params_destroy(params); 27 | if (status < 0) { 28 | printf("failed to connect to Memgraph: %s\n", mg_session_error(session)); 29 | mg_session_destroy(session); 30 | return 1; 31 | } 32 | 33 | if (mg_session_run(session, argv[3], NULL, NULL, NULL, NULL) < 0) { 34 | printf("failed to execute query: %s\n", mg_session_error(session)); 35 | mg_session_destroy(session); 36 | return 1; 37 | } 38 | 39 | if (mg_session_pull(session, NULL)) { 40 | printf("failed to pull results of the query: %s\n", 41 | mg_session_error(session)); 42 | mg_session_destroy(session); 43 | return 1; 44 | } 45 | 46 | mg_result *result; 47 | int rows = 0; 48 | while ((status = mg_session_fetch(session, &result)) == 1) { 49 | rows++; 50 | } 51 | 52 | if (status < 0) { 53 | printf("error occurred during query execution: %s\n", 54 | mg_session_error(session)); 55 | } else { 56 | printf("query executed successfuly and returned %d rows\n", rows); 57 | } 58 | 59 | mg_session_destroy(session); 60 | mg_finalize(); 61 | 62 | return 0; 63 | } 64 | -------------------------------------------------------------------------------- /examples/basic.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | int main(int argc, char *argv[]) { 7 | if (argc != 4) { 8 | std::cerr << "Usage: " << argv[0] << " [host] [port] [query]\n"; 9 | exit(1); 10 | } 11 | 12 | mg::Client::Init(); 13 | 14 | std::cout << "mgclient version: " << mg::Client::Version() << std::endl; 15 | mg::Client::Params params; 16 | params.host = argv[1]; 17 | params.port = static_cast(atoi(argv[2])); 18 | params.use_ssl = false; 19 | auto client = mg::Client::Connect(params); 20 | 21 | if (!client) { 22 | std::cerr << "Failed to connect!\n"; 23 | return 1; 24 | } 25 | 26 | if (!client->Execute(argv[3])) { 27 | std::cerr << "Failed to execute query!"; 28 | return 1; 29 | } 30 | 31 | int rows = 0; 32 | while (const auto maybeResult = client->FetchOne()) { 33 | ++rows; 34 | } 35 | 36 | std::cout << "Fetched " << rows << " row(s)\n"; 37 | 38 | // Deallocate the client because mg_finalize has to be called globally. 39 | client.reset(nullptr); 40 | 41 | mg::Client::Finalize(); 42 | 43 | return 0; 44 | } 45 | -------------------------------------------------------------------------------- /mgclient_cpp/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(mgclient_cpp INTERFACE) 2 | target_include_directories(mgclient_cpp INTERFACE 3 | $ 4 | $ 5 | ) 6 | install(DIRECTORY 7 | "${CMAKE_CURRENT_SOURCE_DIR}/include/" 8 | DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) 9 | target_link_libraries(mgclient_cpp INTERFACE mgclient-static) 10 | -------------------------------------------------------------------------------- /mgclient_cpp/include/mgclient.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2020 Memgraph Ltd. [https://memgraph.com] 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #pragma once 16 | 17 | #include 18 | #include 19 | #include 20 | 21 | #include "mgclient-value.hpp" 22 | #include "mgclient.h" 23 | 24 | namespace mg { 25 | class MgException : public std::exception { 26 | public: 27 | explicit MgException(const std::string_view message) : msg_(message) {} 28 | 29 | const char *what() const noexcept override { return msg_.c_str(); } 30 | 31 | protected: 32 | std::string msg_; 33 | }; 34 | 35 | class ClientException : public MgException { 36 | public: 37 | explicit ClientException(const std::string_view message) 38 | : MgException(message) {} 39 | }; 40 | 41 | class TransientException : public MgException { 42 | public: 43 | explicit TransientException(const std::string_view message) 44 | : MgException(message) {} 45 | }; 46 | 47 | class DatabaseException : public MgException { 48 | public: 49 | explicit DatabaseException(const std::string_view message) 50 | : MgException(message) {} 51 | }; 52 | 53 | /// An interface for a Memgraph client that can execute queries and fetch 54 | /// results. 55 | class Client { 56 | public: 57 | struct Params { 58 | std::string host = "127.0.0.1"; 59 | uint16_t port = 7687; 60 | std::string username = ""; 61 | std::string password = ""; 62 | bool use_ssl = false; 63 | std::string user_agent = "mgclient++/" + std::string(mg_client_version()); 64 | }; 65 | 66 | Client(const Client &) = delete; 67 | Client(Client &&) = default; 68 | Client &operator=(const Client &) = delete; 69 | Client &operator=(Client &&) = delete; 70 | ~Client(); 71 | 72 | /// \brief Client software version. 73 | /// \return client version in the major.minor.patch format. 74 | static const char *Version(); 75 | 76 | /// Initializes the client (the whole process). 77 | /// Should be called at the beginning of each process using the client. 78 | /// 79 | /// \return Zero if initialization was successful. 80 | static int Init(); 81 | 82 | /// Finalizes the client (the whole process). 83 | /// Should be called at the end of each process using the client. 84 | static void Finalize(); 85 | 86 | /// \brief Executes the given Cypher `statement`. 87 | /// \return true when the statement is successfully executed, false otherwise. 88 | /// \note 89 | /// After executing the statement, the method is blocked until all incoming 90 | /// data (execution results) are handled, i.e. until `FetchOne` method returns 91 | /// `std::nullopt`. Even if the result set is empty, the fetching has to be 92 | /// done/finished to be able to execute another statement. 93 | bool Execute(const std::string &statement); 94 | 95 | /// \brief Executes the given Cypher `statement`, supplied with additional 96 | /// `params`. 97 | /// \return true when the statement is successfully executed, false 98 | /// otherwise. 99 | /// \note 100 | /// After executing the statement, the method is blocked 101 | /// until all incoming data (execution results) are handled, i.e. until 102 | /// `FetchOne` method returns `std::nullopt`. 103 | bool Execute(const std::string &statement, const ConstMap ¶ms); 104 | 105 | /// \brief Fetches the next result from the input stream. 106 | /// \return next result from the input stream. 107 | /// If there is nothing to fetch, `std::nullopt` is returned. 108 | std::optional> FetchOne(); 109 | 110 | /// \brief Fetches all results and discards them. 111 | void DiscardAll(); 112 | 113 | /// \brief Fetches all results. 114 | std::optional>> FetchAll(); 115 | 116 | const std::vector &GetColumns() const; 117 | 118 | /// \brief Start a transaction. 119 | /// \return true when the transaction was successfully started, false 120 | /// otherwise. 121 | bool BeginTransaction(); 122 | 123 | /// \brief Commit current transaction. 124 | /// \return true when the transaction was successfully committed, false 125 | /// otherwise. 126 | bool CommitTransaction(); 127 | 128 | /// \brief Rollback current transaction. 129 | /// \return true when the transaction was successfully rollbacked, false 130 | /// otherwise. 131 | bool RollbackTransaction(); 132 | 133 | /// \brief Static method that creates a Memgraph client instance. 134 | /// \return pointer to the created client instance. 135 | /// If the connection couldn't be established given the `params`, it returns 136 | /// a `nullptr`. 137 | static std::unique_ptr Connect(const Params ¶ms); 138 | 139 | private: 140 | explicit Client(mg_session *session); 141 | 142 | mg_session *session_; 143 | std::vector columns_; 144 | }; 145 | 146 | inline std::unique_ptr Client::Connect(const Client::Params ¶ms) { 147 | mg_session_params *mg_params = mg_session_params_make(); 148 | if (!mg_params) { 149 | return nullptr; 150 | } 151 | mg_session_params_set_host(mg_params, params.host.c_str()); 152 | mg_session_params_set_port(mg_params, params.port); 153 | if (!params.username.empty()) { 154 | mg_session_params_set_username(mg_params, params.username.c_str()); 155 | mg_session_params_set_password(mg_params, params.password.c_str()); 156 | } 157 | mg_session_params_set_user_agent(mg_params, params.user_agent.c_str()); 158 | mg_session_params_set_sslmode( 159 | mg_params, params.use_ssl ? MG_SSLMODE_REQUIRE : MG_SSLMODE_DISABLE); 160 | 161 | mg_session *session = nullptr; 162 | int status = mg_connect(mg_params, &session); 163 | mg_session_params_destroy(mg_params); 164 | if (status < 0) { 165 | return nullptr; 166 | } 167 | 168 | // Using `new` to access private constructor. 169 | return std::unique_ptr(new Client(session)); 170 | } 171 | 172 | inline Client::Client(mg_session *session) : session_(session) {} 173 | 174 | inline Client::~Client() { mg_session_destroy(session_); } 175 | 176 | inline const char *Client::Version() { return mg_client_version(); } 177 | 178 | inline int Client::Init() { return mg_init(); } 179 | 180 | inline void Client::Finalize() { mg_finalize(); } 181 | 182 | inline bool Client::Execute(const std::string &statement) { 183 | const mg_list *columns; 184 | int status = mg_session_run(session_, statement.c_str(), nullptr, nullptr, 185 | &columns, nullptr); 186 | if (status < 0) { 187 | return false; 188 | } 189 | 190 | status = mg_session_pull(session_, nullptr); 191 | if (status < 0) { 192 | return false; 193 | } 194 | 195 | const size_t list_length = mg_list_size(columns); 196 | columns_.clear(); 197 | for (size_t i = 0; i < list_length; i++) { 198 | columns_.push_back( 199 | std::string(Value(mg_list_at(columns, i)).ValueString())); 200 | } 201 | 202 | return true; 203 | } 204 | 205 | inline bool Client::Execute(const std::string &statement, 206 | const ConstMap ¶ms) { 207 | const mg_list *columns; 208 | int status = mg_session_run(session_, statement.c_str(), params.ptr(), 209 | nullptr, &columns, nullptr); 210 | if (status < 0) { 211 | return false; 212 | } 213 | 214 | status = mg_session_pull(session_, nullptr); 215 | if (status < 0) { 216 | return false; 217 | } 218 | 219 | const size_t list_length = mg_list_size(columns); 220 | columns_.clear(); 221 | for (size_t i = 0; i < list_length; i++) { 222 | columns_.push_back( 223 | std::string(Value(mg_list_at(columns, i)).ValueString())); 224 | } 225 | 226 | return true; 227 | } 228 | 229 | inline std::optional> Client::FetchOne() { 230 | mg_result *result; 231 | int status = mg_session_fetch(session_, &result); 232 | if (status == MG_ERROR_CLIENT_ERROR) { 233 | throw ClientException(mg_session_error(session_)); 234 | } 235 | 236 | if (status == MG_ERROR_TRANSIENT_ERROR) { 237 | throw TransientException(mg_session_error(session_)); 238 | } 239 | 240 | if (status == MG_ERROR_DATABASE_ERROR) { 241 | throw DatabaseException(mg_session_error(session_)); 242 | } 243 | 244 | if (status != 1) { 245 | return std::nullopt; 246 | } 247 | 248 | std::vector values; 249 | const mg_list *list = mg_result_row(result); 250 | const size_t list_length = mg_list_size(list); 251 | values.reserve(list_length); 252 | for (size_t i = 0; i < list_length; ++i) { 253 | values.emplace_back(Value(mg_list_at(list, i))); 254 | } 255 | return values; 256 | } 257 | 258 | inline void Client::DiscardAll() { while (FetchOne()); } 259 | 260 | inline std::optional>> Client::FetchAll() { 261 | std::vector> data; 262 | while (auto maybe_result = FetchOne()) { 263 | data.emplace_back(std::move(*maybe_result)); 264 | } 265 | return data; 266 | } 267 | 268 | inline const std::vector &Client::GetColumns() const { 269 | return columns_; 270 | } 271 | 272 | inline bool Client::BeginTransaction() { 273 | return mg_session_begin_transaction(session_, nullptr) == 0; 274 | } 275 | 276 | inline bool Client::CommitTransaction() { 277 | mg_result *result; 278 | return mg_session_commit_transaction(session_, &result) == 0; 279 | } 280 | 281 | inline bool Client::RollbackTransaction() { 282 | mg_result *result; 283 | return mg_session_rollback_transaction(session_, &result) == 0; 284 | } 285 | 286 | } // namespace mg 287 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016-2020 Memgraph Ltd. [https://memgraph.com] 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | add_library(project_options INTERFACE) 16 | include(../cmake/Sanitizers.cmake) 17 | enable_sanitizers(project_options) 18 | 19 | set(mgclient_src_files 20 | mgallocator.c 21 | mgclient.c 22 | mgmessage.c 23 | mgsession.c 24 | mgsession-decoder.c 25 | mgsession-encoder.c 26 | mgtransport.c 27 | mgvalue.c) 28 | if(MGCLIENT_ON_APPLE) 29 | list(APPEND mgclient_src_files apple/mgsocket.c) 30 | elseif(MGCLIENT_ON_LINUX) 31 | list(APPEND mgclient_src_files linux/mgsocket.c) 32 | elseif(MGCLIENT_ON_WINDOWS) 33 | list(APPEND mgclient_src_files windows/mgsocket.c) 34 | else() 35 | message(FATAL_ERROR "Operating system undefined.") 36 | endif() 37 | 38 | if(EMSCRIPTEN) 39 | list(APPEND mgclient_src_files mgwasm.c) 40 | add_executable(mgclient ${mgclient_src_files}) 41 | set_target_properties(mgclient PROPERTIES LINK_FLAGS "-s ASYNCIFY=1 -s WASM_BIGINT -s MODULARIZE -s EXPORT_NAME=\"load_mgclient\" --shared-memory --no-entry -s USE_PTHREADS=1 -s WEBSOCKET_SUBPROTOCOL=\"binary\" -s EXPORTED_RUNTIME_METHODS=\"ccall, cwrap, getValue, setValue, UTF8ToString, allocateUTF8\"") 42 | 43 | target_include_directories(mgclient 44 | PRIVATE 45 | "${PROJECT_SOURCE_DIR}/src" 46 | PUBLIC 47 | "${PROJECT_SOURCE_DIR}/include" 48 | "${CMAKE_CURRENT_BINARY_DIR}") 49 | else() 50 | find_package(OpenSSL REQUIRED) 51 | include(GenerateExportHeader) 52 | 53 | add_library(mgclient-static STATIC ${mgclient_src_files}) 54 | 55 | generate_export_header(mgclient-static 56 | BASE_NAME "mgclient" 57 | EXPORT_FILE_NAME "mgclient-export.h") 58 | 59 | set_target_properties(mgclient-static PROPERTIES 60 | OUTPUT_NAME mgclient) 61 | target_compile_definitions(mgclient-static PUBLIC MGCLIENT_STATIC_DEFINE) 62 | target_include_directories(mgclient-static 63 | PRIVATE 64 | "${PROJECT_SOURCE_DIR}/src" 65 | PUBLIC 66 | "${PROJECT_SOURCE_DIR}/include" 67 | "${CMAKE_CURRENT_BINARY_DIR}" 68 | "${OPENSSL_INCLUDE_DIR}") 69 | target_link_libraries(mgclient-static 70 | PRIVATE 71 | ${OPENSSL_LIBRARIES} project_options project_c_warnings) 72 | 73 | if(MGCLIENT_ON_WINDOWS) 74 | target_link_libraries(mgclient-static PUBLIC ws2_32 crypt32 gdi32) 75 | endif() 76 | 77 | add_library(mgclient-shared SHARED ${mgclient_src_files}) 78 | 79 | generate_export_header(mgclient-shared 80 | BASE_NAME "mgclient" 81 | EXPORT_FILE_NAME "mgclient-export.h") 82 | 83 | set_target_properties(mgclient-shared PROPERTIES 84 | OUTPUT_NAME mgclient 85 | SOVERSION ${mgclient_SOVERSION} 86 | C_VISIBILITY_PRESET hidden) 87 | target_include_directories(mgclient-shared 88 | PRIVATE 89 | "${PROJECT_SOURCE_DIR}/src" 90 | PUBLIC 91 | "${PROJECT_SOURCE_DIR}/include" 92 | "${CMAKE_CURRENT_BINARY_DIR}" 93 | "${OPENSSL_INCLUDE_DIR}") 94 | target_link_libraries(mgclient-shared 95 | PRIVATE 96 | ${OPENSSL_LIBRARIES} project_options project_c_warnings) 97 | 98 | if(MGCLIENT_ON_WINDOWS) 99 | target_link_libraries(mgclient-shared PUBLIC ws2_32 crypt32 gdi32) 100 | endif() 101 | 102 | generate_export_header(mgclient-shared 103 | BASE_NAME "mgclient" 104 | EXPORT_FILE_NAME "mgclient-export.h") 105 | 106 | include(GNUInstallDirs) 107 | 108 | install(TARGETS mgclient-static mgclient-shared 109 | ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} 110 | LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} 111 | RUNTIME DESTINATION ${CMAKE_INSTALL_LIBDIR}) 112 | install(DIRECTORY 113 | "${PROJECT_SOURCE_DIR}/include/" 114 | DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) 115 | install(FILES 116 | "${CMAKE_CURRENT_BINARY_DIR}/mgclient-export.h" 117 | DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) 118 | endif() 119 | -------------------------------------------------------------------------------- /src/apple/mgcommon.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2020 Memgraph Ltd. [https://memgraph.com] 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef MGCLIENT_APPLE_MGCOMMON_H 16 | #define MGCLIENT_APPLE_MGCOMMON_H 17 | 18 | #include 19 | 20 | #define htobe16(x) OSSwapHostToBigInt16(x) 21 | #define htole16(x) OSSwapHostToLittleInt16(x) 22 | #define be16toh(x) OSSwapBigToHostInt16(x) 23 | #define le16toh(x) OSSwapLittleToHostInt16(x) 24 | 25 | #define htobe32(x) OSSwapHostToBigInt32(x) 26 | #define htole32(x) OSSwapHostToLittleInt32(x) 27 | #define be32toh(x) OSSwapBigToHostInt32(x) 28 | #define le32toh(x) OSSwapLittleToHostInt32(x) 29 | 30 | #define htobe64(x) OSSwapHostToBigInt64(x) 31 | #define htole64(x) OSSwapHostToLittleInt64(x) 32 | #define be64toh(x) OSSwapBigToHostInt64(x) 33 | #define le64toh(x) OSSwapLittleToHostInt64(x) 34 | 35 | #define __BYTE_ORDER BYTE_ORDER 36 | #define __BIG_ENDIAN BIG_ENDIAN 37 | #define __LITTLE_ENDIAN LITTLE_ENDIAN 38 | #define __PDP_ENDIAN PDP_ENDIAN 39 | 40 | #endif 41 | -------------------------------------------------------------------------------- /src/apple/mgsocket.c: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2020 Memgraph Ltd. [https://memgraph.com] 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "mgsocket.h" 16 | 17 | #include 18 | 19 | #define MG_RETRY_ON_EINTR(expression) \ 20 | __extension__({ \ 21 | long result; \ 22 | do { \ 23 | result = (long)(expression); \ 24 | } while (result == -1L && errno == EINTR); \ 25 | result; \ 26 | }) 27 | 28 | int mg_socket_init(void) { return MG_SUCCESS; } 29 | 30 | int mg_socket_create(int af, int type, int protocol) { 31 | int sockfd = socket(af, type, protocol); 32 | if (sockfd == -1) { 33 | return MG_ERROR_SOCKET; 34 | } 35 | return sockfd; 36 | } 37 | 38 | int mg_socket_create_handle_error(int sock, mg_session *session) { 39 | if (sock == MG_ERROR_SOCKET) { 40 | mg_session_set_error(session, "couldn't open socket: %s", 41 | mg_socket_error()); 42 | return MG_ERROR_NETWORK_FAILURE; 43 | } 44 | return MG_SUCCESS; 45 | } 46 | 47 | int mg_socket_connect(int sock, const struct sockaddr *addr, 48 | socklen_t addrlen) { 49 | long status = MG_RETRY_ON_EINTR(connect(sock, addr, addrlen)); 50 | if (status == -1L) { 51 | return MG_ERROR_SOCKET; 52 | } 53 | return MG_SUCCESS; 54 | } 55 | 56 | int mg_socket_connect_handle_error(int *sock, int status, mg_session *session) { 57 | if (status != MG_SUCCESS) { 58 | mg_session_set_error(session, "couldn't connect to host: %s", 59 | mg_socket_error()); 60 | if (mg_socket_close(*sock) != 0) { 61 | abort(); 62 | } 63 | *sock = MG_ERROR_SOCKET; 64 | return MG_ERROR_NETWORK_FAILURE; 65 | } 66 | return MG_SUCCESS; 67 | } 68 | 69 | int mg_socket_options(int sock, mg_session *session) { 70 | (void)sock; 71 | (void)session; 72 | return MG_SUCCESS; 73 | } 74 | 75 | ssize_t mg_socket_send(int sock, const void *buf, int len) { 76 | return MG_RETRY_ON_EINTR(send(sock, buf, len, 0)); 77 | } 78 | 79 | ssize_t mg_socket_receive(int sock, void *buf, int len) { 80 | return MG_RETRY_ON_EINTR(recv(sock, buf, len, 0)); 81 | } 82 | 83 | int mg_socket_poll(struct pollfd *fds, unsigned int nfds, int timeout) { 84 | return MG_RETRY_ON_EINTR(poll(fds, nfds, timeout)); 85 | } 86 | 87 | int mg_socket_pair(int d, int type, int protocol, int *sv) { 88 | return socketpair(d, type, protocol, sv); 89 | } 90 | 91 | int mg_socket_close(int sock) { return MG_RETRY_ON_EINTR(close(sock)); } 92 | 93 | char *mg_socket_error(void) { return strerror(errno); } 94 | 95 | void mg_socket_finalize(void) {} 96 | -------------------------------------------------------------------------------- /src/linux/mgcommon.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2020 Memgraph Ltd. [https://memgraph.com] 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef MGCLIENT_LINUX_MGCOMMON_H 16 | #define MGCLIENT_LINUX_MGCOMMON_H 17 | 18 | #include 19 | 20 | #endif 21 | -------------------------------------------------------------------------------- /src/linux/mgsocket.c: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2020 Memgraph Ltd. [https://memgraph.com] 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "mgsocket.h" 16 | 17 | #include 18 | #include 19 | 20 | #ifdef __EMSCRIPTEN__ 21 | #include "emscripten.h" 22 | #include "mgwasm.h" 23 | #endif 24 | 25 | #define MG_RETRY_ON_EINTR(expression) \ 26 | __extension__({ \ 27 | long result; \ 28 | do { \ 29 | result = (long)(expression); \ 30 | } while (result == -1L && (errno == EINTR)); \ 31 | result; \ 32 | }) 33 | 34 | // Please refer to https://man7.org/linux/man-pages/man2 for more details about 35 | // Linux system calls. 36 | 37 | int mg_socket_init(void) { return MG_SUCCESS; } 38 | 39 | int mg_socket_create(int af, int type, int protocol) { 40 | int sockfd = socket(af, type, protocol); 41 | if (sockfd == -1) { 42 | return MG_ERROR_SOCKET; 43 | } 44 | return sockfd; 45 | } 46 | int mg_socket_create_handle_error(int sock, mg_session *session) { 47 | if (sock == MG_ERROR_SOCKET) { 48 | mg_session_set_error(session, "couldn't open socket: %s", 49 | mg_socket_error()); 50 | return MG_ERROR_NETWORK_FAILURE; 51 | } 52 | return MG_SUCCESS; 53 | } 54 | 55 | static int mg_socket_test_status_is_error(long status) { 56 | #ifdef __EMSCRIPTEN__ 57 | if (status == -1L && errno != EINPROGRESS) { 58 | #else 59 | if (status == -1L) { 60 | #endif 61 | return 1; 62 | } 63 | return 0; 64 | } 65 | 66 | int mg_socket_connect(int sock, const struct sockaddr *addr, 67 | socklen_t addrlen) { 68 | long status = MG_RETRY_ON_EINTR(connect(sock, addr, addrlen)); 69 | if (mg_socket_test_status_is_error(status)) { 70 | return MG_ERROR_SOCKET; 71 | } 72 | #ifdef __EMSCRIPTEN__ 73 | if (mg_wasm_suspend_until_ready_to_write(sock) == -1) { 74 | return MG_ERROR_SOCKET; 75 | } 76 | #endif 77 | 78 | return MG_SUCCESS; 79 | } 80 | 81 | int mg_socket_connect_handle_error(int *sock, int status, mg_session *session) { 82 | if (status != MG_SUCCESS) { 83 | mg_session_set_error(session, "couldn't connect to host: %s", 84 | mg_socket_error()); 85 | if (mg_socket_close(*sock) != 0) { 86 | abort(); 87 | } 88 | *sock = MG_ERROR_SOCKET; 89 | return MG_ERROR_NETWORK_FAILURE; 90 | } 91 | return MG_SUCCESS; 92 | } 93 | 94 | int mg_socket_options(int sock, mg_session *session) { 95 | #ifdef __EMSCRIPTEN__ 96 | (void)sock; 97 | (void)session; 98 | return MG_SUCCESS; 99 | #else 100 | struct { 101 | int level; 102 | int optname; 103 | int optval; 104 | } socket_options[] = {// disable Nagle algorithm for performance reasons 105 | {SOL_TCP, TCP_NODELAY, 1}, 106 | // turn keep-alive on 107 | {SOL_SOCKET, SO_KEEPALIVE, 1}, 108 | // wait 20s before sending keep-alive packets 109 | {SOL_TCP, TCP_KEEPIDLE, 20}, 110 | // 4 keep-alive packets must fail to close 111 | {SOL_TCP, TCP_KEEPCNT, 4}, 112 | // send keep-alive packets every 15s 113 | {SOL_TCP, TCP_KEEPINTVL, 15}}; 114 | const size_t OPTCNT = sizeof(socket_options) / sizeof(socket_options[0]); 115 | 116 | for (size_t i = 0; i < OPTCNT; ++i) { 117 | int optval = socket_options[i].optval; 118 | socklen_t optlen = sizeof(optval); 119 | 120 | if (setsockopt(sock, socket_options[i].level, socket_options[i].optname, 121 | (void *)&optval, optlen) != 0) { 122 | mg_session_set_error(session, "couldn't set socket option: %s", 123 | mg_socket_error()); 124 | if (mg_socket_close(sock) != 0) { 125 | abort(); 126 | } 127 | return MG_ERROR_NETWORK_FAILURE; 128 | } 129 | } 130 | return MG_SUCCESS; 131 | #endif 132 | } 133 | 134 | ssize_t mg_socket_send(int sock, const void *buf, int len) { 135 | return MG_RETRY_ON_EINTR(send(sock, buf, len, MSG_NOSIGNAL)); 136 | } 137 | 138 | ssize_t mg_socket_receive(int sock, void *buf, int len) { 139 | return MG_RETRY_ON_EINTR(recv(sock, buf, len, 0)); 140 | } 141 | 142 | int mg_socket_poll(struct pollfd *fds, unsigned int nfds, int timeout) { 143 | return MG_RETRY_ON_EINTR(poll(fds, nfds, timeout)); 144 | } 145 | 146 | int mg_socket_pair(int d, int type, int protocol, int *sv) { 147 | return socketpair(d, type, protocol, sv); 148 | } 149 | 150 | int mg_socket_close(int sock) { return MG_RETRY_ON_EINTR(close(sock)); } 151 | 152 | char *mg_socket_error(void) { return strerror(errno); } 153 | 154 | void mg_socket_finalize(void) {} 155 | -------------------------------------------------------------------------------- /src/mgallocator.c: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2020 Memgraph Ltd. [https://memgraph.com] 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "mgallocator.h" 16 | 17 | #include 18 | // Somehow stdalign.h is not there (VS Build Tools 2019, Windows 11) 19 | // https://docs.microsoft.com/en-us/cpp/cpp/alignment-cpp-declarations?view=msvc-170 20 | // EVERYWHERE EXCEPT MSVC 21 | #if !defined(_WIN32) || !defined(_MSC_VER) 22 | #include 23 | #endif 24 | // ONLY ON MSVC 25 | #ifdef _MSC_VER 26 | #define alignof __alignof 27 | typedef double max_align_t; 28 | #endif 29 | #include 30 | #include 31 | #include 32 | 33 | void *mg_system_realloc(struct mg_allocator *self, void *buf, size_t size) { 34 | (void)self; 35 | return realloc(buf, size); 36 | } 37 | 38 | void *mg_system_malloc(struct mg_allocator *self, size_t size) { 39 | (void)self; 40 | return malloc(size); 41 | } 42 | 43 | void mg_system_free(struct mg_allocator *self, void *buf) { 44 | (void)self; 45 | free(buf); 46 | } 47 | 48 | void *mg_allocator_malloc(struct mg_allocator *allocator, size_t size) { 49 | return allocator->malloc(allocator, size); 50 | } 51 | 52 | void *mg_allocator_realloc(struct mg_allocator *allocator, void *buf, 53 | size_t size) { 54 | return allocator->realloc(allocator, buf, size); 55 | } 56 | 57 | void mg_allocator_free(struct mg_allocator *allocator, void *buf) { 58 | allocator->free(allocator, buf); 59 | } 60 | 61 | struct mg_allocator mg_system_allocator = {mg_system_malloc, mg_system_realloc, 62 | mg_system_free}; 63 | 64 | typedef struct mg_memory_block { 65 | char *buffer; 66 | struct mg_memory_block *next; 67 | } mg_memory_block; 68 | 69 | mg_memory_block *mg_memory_block_alloc(mg_allocator *allocator, size_t size) { 70 | /// Ensure that the memory is properly aligned. 71 | _Static_assert( 72 | sizeof(mg_memory_block) % alignof(max_align_t) == 0, 73 | "Size of mg_memory_block doesn't satisfy alignment requirements"); 74 | mg_memory_block *block = 75 | mg_allocator_malloc(allocator, sizeof(mg_memory_block) + size); 76 | if (!block) { 77 | return NULL; 78 | } 79 | block->next = NULL; 80 | block->buffer = (char *)block + sizeof(mg_memory_block); 81 | return block; 82 | } 83 | 84 | typedef struct mg_linear_allocator { 85 | void *(*malloc)(struct mg_allocator *self, size_t size); 86 | void *(*realloc)(struct mg_allocator *self, void *buf, size_t size); 87 | void (*free)(struct mg_allocator *self, void *buf); 88 | 89 | mg_memory_block *current_block; 90 | size_t current_offset; 91 | 92 | const size_t block_size; 93 | const size_t sep_alloc_threshold; 94 | 95 | mg_allocator *underlying_allocator; 96 | } mg_linear_allocator; 97 | 98 | void *mg_linear_allocator_malloc(struct mg_allocator *allocator, size_t size) { 99 | mg_linear_allocator *self = (mg_linear_allocator *)allocator; 100 | 101 | if (size >= self->sep_alloc_threshold) { 102 | // Make a new block, but put it below the first block so we don't waste 103 | // bytes in it. 104 | mg_memory_block *new_block = 105 | mg_memory_block_alloc(self->underlying_allocator, size); 106 | new_block->next = self->current_block->next; 107 | self->current_block->next = new_block; 108 | return new_block->buffer; 109 | } 110 | 111 | if (self->current_offset + size > self->block_size) { 112 | // Create a new block and put it at the beginning of the list. 113 | mg_memory_block *new_block = 114 | mg_memory_block_alloc(self->underlying_allocator, self->block_size); 115 | new_block->next = self->current_block; 116 | self->current_block = new_block; 117 | self->current_offset = 0; 118 | } 119 | 120 | assert(self->current_offset + size <= self->block_size); 121 | assert(self->current_offset % alignof(max_align_t) == 0); 122 | 123 | void *ret = self->current_block->buffer + self->current_offset; 124 | self->current_offset += size; 125 | if (self->current_offset % alignof(max_align_t) != 0) { 126 | self->current_offset += 127 | alignof(max_align_t) - (self->current_offset % alignof(max_align_t)); 128 | } 129 | return ret; 130 | } 131 | 132 | void *mg_linear_allocator_realloc(struct mg_allocator *allocator, void *buf, 133 | size_t size) { 134 | (void)allocator; 135 | (void)buf; 136 | (void)size; 137 | fprintf(stderr, "mg_linear_allocator doesn't support realloc\n"); 138 | return NULL; 139 | } 140 | 141 | void mg_linear_allocator_free(struct mg_allocator *allocator, void *buf) { 142 | (void)allocator; 143 | (void)buf; 144 | return; 145 | } 146 | 147 | mg_linear_allocator *mg_linear_allocator_init(mg_allocator *allocator, 148 | size_t block_size, 149 | size_t sep_alloc_threshold) { 150 | mg_memory_block *first_block = mg_memory_block_alloc(allocator, block_size); 151 | if (!first_block) { 152 | return NULL; 153 | } 154 | 155 | mg_linear_allocator tmp_alloc = {mg_linear_allocator_malloc, 156 | mg_linear_allocator_realloc, 157 | mg_linear_allocator_free, 158 | first_block, 159 | 0, 160 | block_size, 161 | sep_alloc_threshold, 162 | allocator}; 163 | mg_linear_allocator *alloc = 164 | mg_allocator_malloc(allocator, sizeof(mg_linear_allocator)); 165 | if (!alloc) { 166 | mg_allocator_free(allocator, first_block); 167 | return NULL; 168 | } 169 | 170 | memcpy(alloc, &tmp_alloc, sizeof(mg_linear_allocator)); 171 | return alloc; 172 | } 173 | 174 | void mg_linear_allocator_destroy(mg_linear_allocator *allocator) { 175 | if (allocator == NULL) { 176 | return; 177 | } 178 | while (allocator->current_block) { 179 | mg_memory_block *next_block = allocator->current_block->next; 180 | mg_allocator_free(allocator->underlying_allocator, 181 | allocator->current_block); 182 | allocator->current_block = next_block; 183 | } 184 | mg_allocator_free(allocator->underlying_allocator, allocator); 185 | } 186 | 187 | void mg_linear_allocator_reset(mg_linear_allocator *allocator) { 188 | // The first block is always of size allocator->block_size. We will keep that 189 | // one and free the others. 190 | mg_memory_block *first_block = allocator->current_block; 191 | while (first_block->next) { 192 | mg_memory_block *new_next = first_block->next->next; 193 | mg_allocator_free(allocator->underlying_allocator, first_block->next); 194 | first_block->next = new_next; 195 | } 196 | allocator->current_offset = 0; 197 | } 198 | -------------------------------------------------------------------------------- /src/mgallocator.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2020 Memgraph Ltd. [https://memgraph.com] 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef MGCLIENT_MGALLOCATOR_H 16 | #define MGCLIENT_MGALLOCATOR_H 17 | 18 | #ifdef __cplusplus 19 | extern "C" { 20 | #endif 21 | 22 | #include 23 | 24 | typedef struct mg_allocator { 25 | void *(*malloc)(struct mg_allocator *self, size_t size); 26 | void *(*realloc)(struct mg_allocator *self, void *buf, size_t size); 27 | void (*free)(struct mg_allocator *self, void *buf); 28 | } mg_allocator; 29 | 30 | void *mg_allocator_malloc(struct mg_allocator *allocator, size_t size); 31 | 32 | void *mg_allocator_realloc(struct mg_allocator *allocator, void *buf, 33 | size_t size); 34 | 35 | void mg_allocator_free(struct mg_allocator *allocator, void *buf); 36 | 37 | /// A special allocator used for decoding of Bolt messages 38 | /// (more or less copied from libpq's PGResult allocator). 39 | /// 40 | /// All decoded objects are released as soon as the next Bolt message is fetched 41 | /// from the network buffer, so we don't need the ability to release them 42 | /// individually. Therefore, instead of doing a single malloc for each decoded 43 | /// value, memory is allocated in large blocks from the underlying allocator and 44 | /// decoded values are placed inside those blocks. When there is no more 45 | /// available space in the current block, a new one is allocated. This 46 | /// should significantly reduce the amount of malloc calls done by the client. 47 | // 48 | /// Internally, `mg_linear_allocator` keeps a singly linked list of allocated 49 | /// blocks. Only the head block is a candidate for inserting more data, any 50 | /// extra space in other blocks is wasted. However, this is not too bad since 51 | /// the wasted memory will be released as soon as the next message is fetched. 52 | /// We could try to be smarter and iterate through all blocks to avoid wasting 53 | /// memory but it is probably not worth it. 54 | /// 55 | /// Memory returned by the allocator will be aligned as max_align_t. 56 | /// 57 | /// Allocator is tuned with the following constructor parameters: 58 | /// - block_size: size of the standard allocation block, 59 | /// - sep_alloc_threshold: objects bigger that this size are given their 60 | /// separate blocks, as this will prevent wasting too 61 | /// much memory (the maximum amount of wasted memory per 62 | /// block is sep_alloc_threshold, not including padding). 63 | /// 64 | /// When an objects gets its separate block, it will be placed as the second 65 | /// element of the linked list, so we don't waste leftover space in the first 66 | /// element. 67 | /// 68 | /// Memory from the allocator is freed using `mg_linear_allocator_reset`. It 69 | /// only keeps one empty block of size `block_size` to avoid allocating for each 70 | /// row in the common case when the entire result row fits in a single block. 71 | typedef struct mg_linear_allocator mg_linear_allocator; 72 | 73 | mg_linear_allocator *mg_linear_allocator_init(mg_allocator *allocator, 74 | size_t block_size, 75 | size_t sep_alloc_threshold); 76 | 77 | void mg_linear_allocator_reset(mg_linear_allocator *allocator); 78 | 79 | void mg_linear_allocator_destroy(mg_linear_allocator *allocator); 80 | 81 | extern struct mg_allocator mg_system_allocator; 82 | 83 | #ifdef __cplusplus 84 | } 85 | #endif 86 | 87 | #endif /* MGCLIENT_MGALLOCATOR_H */ 88 | -------------------------------------------------------------------------------- /src/mgcommon.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2020 Memgraph Ltd. [https://memgraph.com] 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef MGCLIENT_MGCOMMON_H 16 | #define MGCLIENT_MGCOMMON_H 17 | 18 | #ifdef __cplusplus 19 | extern "C" { 20 | #endif 21 | 22 | #ifdef MGCLIENT_ON_APPLE 23 | #include "apple/mgcommon.h" 24 | #endif // MGCLIENT_ON_APPLE 25 | 26 | #ifdef MGCLIENT_ON_LINUX 27 | #include "linux/mgcommon.h" 28 | #endif // MGCLIENT_ON_LINUX 29 | 30 | #ifdef MGCLIENT_ON_WINDOWS 31 | #include "windows/mgcommon.h" 32 | #endif // MGCLIENT_ON_WINDOWS 33 | 34 | #define MG_RETURN_IF_FAILED(expression) \ 35 | do { \ 36 | int status = (expression); \ 37 | if (status != 0) { \ 38 | return status; \ 39 | } \ 40 | } while (0) 41 | 42 | #ifdef NDEBUG 43 | #define DB_ACTIVE 0 44 | #else 45 | #define DB_ACTIVE 1 46 | #endif // NDEBUG 47 | #define DB_LOG(x) \ 48 | do { \ 49 | if (DB_ACTIVE) fprintf(stderr, x); \ 50 | } while (0) 51 | 52 | #ifdef MGCLIENT_ON_APPLE 53 | #define MG_ATTRIBUTE_WEAK __attribute__((weak)) 54 | #else 55 | #define MG_ATTRIBUTE_WEAK 56 | #endif 57 | 58 | #ifdef __cplusplus 59 | } 60 | #endif 61 | 62 | #endif /* MGCLIENT_MGCOMMON_H */ 63 | -------------------------------------------------------------------------------- /src/mgconstants.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2020 Memgraph Ltd. [https://memgraph.com] 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef MGCLIENT_MGCONSTANTS_H 16 | #define MGCLIENT_MGCONSTANTS_H 17 | 18 | #ifdef __cplusplus 19 | extern "C" { 20 | #endif 21 | 22 | #include 23 | #include 24 | 25 | #define MG_BOLT_CHUNK_HEADER_SIZE 2 26 | #define MG_BOLT_MAX_CHUNK_SIZE 65535 27 | 28 | #define MG_TINY_INT_MIN -16 29 | #define MG_TINY_INT_MAX 127 30 | 31 | #define MG_TINY_SIZE_MAX 15 32 | 33 | static const char MG_HANDSHAKE_MAGIC[] = "\x60\x60\xB0\x17"; 34 | static const char MG_USER_AGENT[] = "mgclient/" MGCLIENT_VERSION; 35 | 36 | /// Markers 37 | #define MG_MARKER_NULL 0xC0 38 | #define MG_MARKER_BOOL_FALSE 0xC2 39 | #define MG_MARKER_BOOL_TRUE 0xC3 40 | 41 | #define MG_MARKER_INT_8 0xC8 42 | #define MG_MARKER_INT_16 0xC9 43 | #define MG_MARKER_INT_32 0xCA 44 | #define MG_MARKER_INT_64 0xCB 45 | 46 | #define MG_MARKER_FLOAT 0xC1 47 | 48 | #define MG_MARKER_TINY_STRING 0x80 49 | #define MG_MARKER_STRING_8 0xD0 50 | #define MG_MARKER_STRING_16 0xD1 51 | #define MG_MARKER_STRING_32 0xD2 52 | 53 | #define MG_MARKER_TINY_LIST 0x90 54 | #define MG_MARKER_LIST_8 0xD4 55 | #define MG_MARKER_LIST_16 0xD5 56 | #define MG_MARKER_LIST_32 0xD6 57 | 58 | #define MG_MARKER_TINY_MAP 0xA0 59 | #define MG_MARKER_MAP_8 0xD8 60 | #define MG_MARKER_MAP_16 0xD9 61 | #define MG_MARKER_MAP_32 0xDA 62 | 63 | // These have to be ordered from smallest to largest because 64 | // `mg_session_write_container_size` and `mg_session_read_container_size` depend 65 | // on that. 66 | static const uint8_t MG_MARKERS_STRING[] = { 67 | MG_MARKER_TINY_STRING, MG_MARKER_STRING_8, MG_MARKER_STRING_16, 68 | MG_MARKER_STRING_32}; 69 | 70 | static const uint8_t MG_MARKERS_LIST[] = {MG_MARKER_TINY_LIST, MG_MARKER_LIST_8, 71 | MG_MARKER_LIST_16, MG_MARKER_LIST_32}; 72 | 73 | static const uint8_t MG_MARKERS_MAP[] = {MG_MARKER_TINY_MAP, MG_MARKER_MAP_8, 74 | MG_MARKER_MAP_16, MG_MARKER_MAP_32}; 75 | 76 | #define MG_MARKER_TINY_STRUCT 0xB0 77 | #define MG_MARKER_TINY_STRUCT1 0xB1 78 | #define MG_MARKER_TINY_STRUCT2 0xB2 79 | #define MG_MARKER_TINY_STRUCT3 0xB3 80 | #define MG_MARKER_TINY_STRUCT4 0xB4 81 | #define MG_MARKER_TINY_STRUCT5 0xB5 82 | #define MG_MARKER_STRUCT_8 0xDC 83 | #define MG_MARKER_STRUCT_16 0xDD 84 | 85 | // Struct signatures 86 | #define MG_SIGNATURE_NODE 0x4E 87 | #define MG_SIGNATURE_RELATIONSHIP 0x52 88 | #define MG_SIGNATURE_UNBOUND_RELATIONSHIP 0x72 89 | #define MG_SIGNATURE_PATH 0x50 90 | #define MG_SIGNATURE_DATE 0x44 91 | #define MG_SIGNATURE_TIME 0x54 92 | #define MG_SIGNATURE_LOCAL_TIME 0x74 93 | #define MG_SIGNATURE_DATE_TIME 0x46 94 | #define MG_SIGNATURE_DATE_TIME_ZONE_ID 0x66 95 | #define MG_SIGNATURE_LOCAL_DATE_TIME 0x64 96 | #define MG_SIGNATURE_DURATION 0x45 97 | #define MG_SIGNATURE_POINT_2D 0x58 98 | #define MG_SIGNATURE_POINT_3D 0x59 99 | #define MG_SIGNATURE_MESSAGE_HELLO 0x01 100 | #define MG_SIGNATURE_MESSAGE_RUN 0x10 101 | #define MG_SIGNATURE_MESSAGE_PULL 0x3F 102 | #define MG_SIGNATURE_MESSAGE_RECORD 0x71 103 | #define MG_SIGNATURE_MESSAGE_SUCCESS 0x70 104 | #define MG_SIGNATURE_MESSAGE_FAILURE 0x7F 105 | #define MG_SIGNATURE_MESSAGE_ACK_FAILURE 0x0E 106 | #define MG_SIGNATURE_MESSAGE_RESET 0x0F 107 | #define MG_SIGNATURE_MESSAGE_BEGIN 0x11 108 | #define MG_SIGNATURE_MESSAGE_COMMIT 0x12 109 | #define MG_SIGNATURE_MESSAGE_ROLLBACK 0x13 110 | 111 | #ifdef __cplusplus 112 | } 113 | #endif 114 | 115 | #endif /* MGCLIENT_MGCONSTANTS_H */ 116 | -------------------------------------------------------------------------------- /src/mgmessage.c: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2020 Memgraph Ltd. [https://memgraph.com] 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "mgmessage.h" 16 | 17 | void mg_message_success_destroy_ca(mg_message_success *message, 18 | mg_allocator *allocator) { 19 | if (!message) return; 20 | mg_map_destroy_ca(message->metadata, allocator); 21 | mg_allocator_free(allocator, message); 22 | } 23 | 24 | void mg_message_failure_destroy_ca(mg_message_failure *message, 25 | mg_allocator *allocator) { 26 | if (!message) return; 27 | mg_map_destroy_ca(message->metadata, allocator); 28 | mg_allocator_free(allocator, message); 29 | } 30 | 31 | void mg_message_record_destroy_ca(mg_message_record *message, 32 | mg_allocator *allocator) { 33 | if (!message) return; 34 | mg_list_destroy_ca(message->fields, allocator); 35 | mg_allocator_free(allocator, message); 36 | } 37 | 38 | void mg_message_init_destroy_ca(mg_message_init *message, 39 | mg_allocator *allocator) { 40 | if (!message) return; 41 | mg_string_destroy_ca(message->client_name, allocator); 42 | mg_map_destroy_ca(message->auth_token, allocator); 43 | mg_allocator_free(allocator, message); 44 | } 45 | 46 | void mg_message_hello_destroy_ca(mg_message_hello *message, 47 | mg_allocator *allocator) { 48 | if (!message) return; 49 | mg_map_destroy_ca(message->extra, allocator); 50 | mg_allocator_free(allocator, message); 51 | } 52 | 53 | void mg_message_run_destroy_ca(mg_message_run *message, 54 | mg_allocator *allocator) { 55 | if (!message) return; 56 | mg_string_destroy_ca(message->statement, allocator); 57 | mg_map_destroy_ca(message->parameters, allocator); 58 | mg_map_destroy_ca(message->extra, allocator); 59 | mg_allocator_free(allocator, message); 60 | } 61 | 62 | void mg_message_begin_destroy_ca(mg_message_begin *message, 63 | mg_allocator *allocator) { 64 | if (!message) { 65 | return; 66 | } 67 | mg_map_destroy_ca(message->extra, allocator); 68 | mg_allocator_free(allocator, message); 69 | } 70 | 71 | void mg_message_pull_destroy_ca(mg_message_pull *message, 72 | mg_allocator *allocator) { 73 | if (!message) { 74 | return; 75 | } 76 | mg_map_destroy_ca(message->extra, allocator); 77 | mg_allocator_free(allocator, message); 78 | } 79 | 80 | void mg_message_destroy_ca(mg_message *message, mg_allocator *allocator) { 81 | if (!message) return; 82 | switch (message->type) { 83 | case MG_MESSAGE_TYPE_SUCCESS: 84 | mg_message_success_destroy_ca(message->success_v, allocator); 85 | break; 86 | case MG_MESSAGE_TYPE_FAILURE: 87 | mg_message_failure_destroy_ca(message->failure_v, allocator); 88 | break; 89 | case MG_MESSAGE_TYPE_RECORD: 90 | mg_message_record_destroy_ca(message->record_v, allocator); 91 | break; 92 | case MG_MESSAGE_TYPE_INIT: 93 | mg_message_init_destroy_ca(message->init_v, allocator); 94 | break; 95 | case MG_MESSAGE_TYPE_HELLO: 96 | mg_message_hello_destroy_ca(message->hello_v, allocator); 97 | break; 98 | case MG_MESSAGE_TYPE_RUN: 99 | mg_message_run_destroy_ca(message->run_v, allocator); 100 | break; 101 | case MG_MESSAGE_TYPE_BEGIN: 102 | mg_message_begin_destroy_ca(message->begin_v, allocator); 103 | break; 104 | case MG_MESSAGE_TYPE_PULL: 105 | mg_message_pull_destroy_ca(message->pull_v, allocator); 106 | break; 107 | case MG_MESSAGE_TYPE_ACK_FAILURE: 108 | case MG_MESSAGE_TYPE_RESET: 109 | case MG_MESSAGE_TYPE_COMMIT: 110 | case MG_MESSAGE_TYPE_ROLLBACK: 111 | break; 112 | } 113 | mg_allocator_free(allocator, message); 114 | } 115 | -------------------------------------------------------------------------------- /src/mgmessage.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2020 Memgraph Ltd. [https://memgraph.com] 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef MGCLIENT_MGMESSAGE_H 16 | #define MGCLIENT_MGMESSAGE_H 17 | 18 | #ifdef __cplusplus 19 | extern "C" { 20 | #endif 21 | 22 | #include "mgvalue.h" 23 | 24 | // Some of these message types are never sent/received by client, but we still 25 | // have them here for testing. 26 | enum mg_message_type { 27 | MG_MESSAGE_TYPE_RECORD, 28 | MG_MESSAGE_TYPE_SUCCESS, 29 | MG_MESSAGE_TYPE_FAILURE, 30 | MG_MESSAGE_TYPE_INIT, 31 | MG_MESSAGE_TYPE_HELLO, 32 | MG_MESSAGE_TYPE_RUN, 33 | MG_MESSAGE_TYPE_ACK_FAILURE, 34 | MG_MESSAGE_TYPE_RESET, 35 | MG_MESSAGE_TYPE_PULL, 36 | MG_MESSAGE_TYPE_BEGIN, 37 | MG_MESSAGE_TYPE_COMMIT, 38 | MG_MESSAGE_TYPE_ROLLBACK 39 | }; 40 | 41 | typedef struct mg_message_success { 42 | mg_map *metadata; 43 | } mg_message_success; 44 | 45 | typedef struct mg_message_failure { 46 | mg_map *metadata; 47 | } mg_message_failure; 48 | 49 | typedef struct mg_message_record { 50 | mg_list *fields; 51 | } mg_message_record; 52 | 53 | typedef struct mg_message_init { 54 | mg_string *client_name; 55 | mg_map *auth_token; 56 | } mg_message_init; 57 | 58 | typedef struct mg_message_hello { 59 | mg_map *extra; 60 | } mg_message_hello; 61 | 62 | typedef struct mg_message_run { 63 | mg_string *statement; 64 | mg_map *parameters; 65 | mg_map *extra; 66 | } mg_message_run; 67 | 68 | typedef struct mg_message_begin { 69 | mg_map *extra; 70 | } mg_message_begin; 71 | 72 | typedef struct mg_message_pull { 73 | mg_map *extra; 74 | } mg_message_pull; 75 | 76 | typedef struct mg_message { 77 | enum mg_message_type type; 78 | union { 79 | mg_message_success *success_v; 80 | mg_message_failure *failure_v; 81 | mg_message_record *record_v; 82 | mg_message_init *init_v; 83 | mg_message_hello *hello_v; 84 | mg_message_run *run_v; 85 | mg_message_begin *begin_v; 86 | mg_message_pull *pull_v; 87 | }; 88 | } mg_message; 89 | 90 | void mg_message_destroy_ca(mg_message *message, mg_allocator *allocator); 91 | 92 | #ifdef __cplusplus 93 | } 94 | #endif 95 | 96 | #endif /* MGCLIENT_MGMESSAGE_H */ 97 | -------------------------------------------------------------------------------- /src/mgsession.c: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2020 Memgraph Ltd. [https://memgraph.com] 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "mgsession.h" 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | // Somehow stdalign.h is not there (VS Build Tools 2019, Windows 11) 25 | // https://docs.microsoft.com/en-us/cpp/cpp/alignment-cpp-declarations?view=msvc-170 26 | // EVERYWHERE EXCEPT MSVC 27 | #if !defined(_WIN32) || !defined(_MSC_VER) 28 | #include 29 | #include 30 | #endif 31 | 32 | #include "mgcommon.h" 33 | #include "mgconstants.h" 34 | #include "mgtransport.h" 35 | 36 | int mg_session_status(const mg_session *session) { 37 | if (!session) { 38 | return MG_SESSION_BAD; 39 | } 40 | return session->status; 41 | } 42 | 43 | #define MG_DECODER_ALLOCATOR_BLOCK_SIZE 131072 44 | // This seems like a reasonable value --- at most 4 kB per block is wasted 45 | // (around 3% of allocated memory, not including padding), and separate 46 | // allocations should only happen for really big objects (lists with more than 47 | // 500 elements, maps with more than 250 elements, strings with more than 4000 48 | // characters, ...). 49 | #define MG_DECODER_SEP_ALLOC_THRESHOLD 4096 50 | 51 | mg_session *mg_session_init(mg_allocator *allocator) { 52 | mg_linear_allocator *decoder_allocator = 53 | mg_linear_allocator_init(allocator, MG_DECODER_ALLOCATOR_BLOCK_SIZE, 54 | MG_DECODER_SEP_ALLOC_THRESHOLD); 55 | if (!decoder_allocator) { 56 | return NULL; 57 | } 58 | 59 | mg_session *session = mg_allocator_malloc(allocator, sizeof(mg_session)); 60 | if (!session) { 61 | mg_linear_allocator_destroy(decoder_allocator); 62 | return NULL; 63 | } 64 | 65 | session->transport = NULL; 66 | session->allocator = allocator; 67 | session->decoder_allocator = (mg_allocator *)decoder_allocator; 68 | session->out_buffer = NULL; 69 | session->in_buffer = NULL; 70 | session->out_capacity = MG_BOLT_CHUNK_HEADER_SIZE + MG_BOLT_MAX_CHUNK_SIZE; 71 | session->out_buffer = mg_allocator_malloc(allocator, session->out_capacity); 72 | if (!session->out_buffer) { 73 | goto cleanup; 74 | } 75 | session->out_begin = MG_BOLT_CHUNK_HEADER_SIZE; 76 | session->out_end = session->out_begin; 77 | 78 | session->in_capacity = MG_BOLT_MAX_CHUNK_SIZE; 79 | session->in_buffer = mg_allocator_malloc(allocator, session->in_capacity); 80 | if (!session->in_buffer) { 81 | goto cleanup; 82 | } 83 | session->in_end = 0; 84 | session->in_cursor = 0; 85 | 86 | session->result.session = session; 87 | session->result.message = NULL; 88 | session->result.columns = NULL; 89 | 90 | session->explicit_transaction = 0; 91 | session->query_number = 0; 92 | 93 | session->error_buffer[0] = 0; 94 | 95 | return session; 96 | 97 | cleanup: 98 | mg_linear_allocator_destroy(decoder_allocator); 99 | mg_allocator_free(allocator, session->in_buffer); 100 | mg_allocator_free(allocator, session->out_buffer); 101 | mg_allocator_free(allocator, session); 102 | return NULL; 103 | } 104 | 105 | void mg_session_set_error(mg_session *session, const char *fmt, ...) { 106 | va_list arglist; 107 | va_start(arglist, fmt); 108 | if (vsnprintf(session->error_buffer, MG_MAX_ERROR_SIZE, fmt, arglist) < 0) { 109 | strncpy(session->error_buffer, "couldn't set error message", 110 | MG_MAX_ERROR_SIZE); 111 | } 112 | va_end(arglist); 113 | } 114 | 115 | const char *mg_session_error(mg_session *session) { 116 | if (!session) { 117 | return "session is NULL (possibly out of memory)"; 118 | } 119 | return session->error_buffer; 120 | } 121 | 122 | void mg_session_invalidate(mg_session *session) { 123 | if (session->transport) { 124 | mg_transport_destroy(session->transport); 125 | session->transport = NULL; 126 | } 127 | session->status = MG_SESSION_BAD; 128 | } 129 | 130 | void mg_session_destroy(mg_session *session) { 131 | if (!session) { 132 | return; 133 | } 134 | if (session->transport) { 135 | mg_transport_destroy(session->transport); 136 | } 137 | mg_allocator_free(session->allocator, session->in_buffer); 138 | mg_allocator_free(session->allocator, session->out_buffer); 139 | 140 | mg_message_destroy_ca(session->result.message, session->decoder_allocator); 141 | session->result.message = NULL; 142 | mg_list_destroy_ca(session->result.columns, session->allocator); 143 | session->result.columns = NULL; 144 | 145 | mg_linear_allocator_destroy( 146 | (mg_linear_allocator *)session->decoder_allocator); 147 | mg_allocator_free(session->allocator, session); 148 | } 149 | 150 | int mg_session_flush_chunk(mg_session *session) { 151 | size_t chunk_size = session->out_end - session->out_begin; 152 | if (!chunk_size) { 153 | return 0; 154 | } 155 | if (chunk_size > MG_BOLT_MAX_CHUNK_SIZE) { 156 | abort(); 157 | } 158 | 159 | // Actual chunk data is written with offset of two bytes, leaving 2 bytes for 160 | // chunk size which we write here before sending. 161 | assert(session->out_begin == MG_BOLT_CHUNK_HEADER_SIZE); 162 | assert(MG_BOLT_CHUNK_HEADER_SIZE == sizeof(uint16_t)); 163 | 164 | *(uint16_t *)session->out_buffer = htobe16((uint16_t)chunk_size); 165 | 166 | if (mg_transport_send(session->transport, session->out_buffer, 167 | session->out_end) != 0) { 168 | mg_session_set_error(session, "failed to send chunk data"); 169 | return MG_ERROR_SEND_FAILED; 170 | } 171 | 172 | session->out_end = session->out_begin; 173 | return 0; 174 | } 175 | 176 | int mg_session_flush_message(mg_session *session) { 177 | { 178 | int status = mg_session_flush_chunk(session); 179 | if (status != 0) { 180 | return status; 181 | } 182 | } 183 | const char MESSAGE_END[] = {0x00, 0x00}; 184 | { 185 | int status = 186 | mg_transport_send(session->transport, MESSAGE_END, sizeof(MESSAGE_END)); 187 | if (status != 0) { 188 | mg_session_set_error(session, "failed to send message end marker"); 189 | return MG_ERROR_SEND_FAILED; 190 | } 191 | } 192 | return 0; 193 | } 194 | 195 | int mg_session_write_raw(mg_session *session, const char *data, size_t len) { 196 | size_t sent = 0; 197 | while (sent < len) { 198 | size_t buffer_free = session->out_capacity - session->out_end; 199 | if (len - sent >= buffer_free) { 200 | memcpy(session->out_buffer + session->out_end, data + sent, buffer_free); 201 | session->out_end = session->out_capacity; 202 | sent += buffer_free; 203 | { 204 | int status = mg_session_flush_chunk(session); 205 | if (status != 0) { 206 | return status; 207 | } 208 | } 209 | } else { 210 | memcpy(session->out_buffer + session->out_end, data + sent, len - sent); 211 | session->out_end += len - sent; 212 | sent = len; 213 | } 214 | } 215 | return 0; 216 | } 217 | 218 | int mg_session_ensure_space_for_chunk(mg_session *session, size_t chunk_size) { 219 | while (session->in_capacity - session->in_end < chunk_size) { 220 | char *new_in_buffer = mg_allocator_realloc( 221 | session->allocator, session->in_buffer, 2 * session->in_capacity); 222 | if (!new_in_buffer) { 223 | mg_session_set_error(session, 224 | "failed to enlarge incoming message buffer"); 225 | return MG_ERROR_OOM; 226 | } 227 | session->in_capacity = 2 * session->in_capacity; 228 | session->in_buffer = new_in_buffer; 229 | } 230 | return 0; 231 | } 232 | 233 | int mg_session_read_chunk(mg_session *session) { 234 | uint16_t chunk_size; 235 | mg_transport_suspend_until_ready_to_read(session->transport); 236 | if (mg_transport_recv(session->transport, (char *)&chunk_size, 2) != 0) { 237 | mg_session_set_error(session, "failed to receive chunk size"); 238 | return MG_ERROR_RECV_FAILED; 239 | } 240 | chunk_size = be16toh(chunk_size); 241 | if (chunk_size == 0) { 242 | return 0; 243 | } 244 | { 245 | int status = mg_session_ensure_space_for_chunk(session, chunk_size); 246 | if (status != 0) { 247 | return status; 248 | } 249 | } 250 | mg_transport_suspend_until_ready_to_read(session->transport); 251 | if (mg_transport_recv(session->transport, 252 | session->in_buffer + session->in_end, 253 | chunk_size) != 0) { 254 | mg_session_set_error(session, "failed to receive chunk data"); 255 | return MG_ERROR_RECV_FAILED; 256 | } 257 | session->in_end += chunk_size; 258 | return 1; 259 | } 260 | 261 | int mg_session_receive_message(mg_session *session) { 262 | // At this point, we reset the session decoder allocator and all objects from 263 | // the previous message are lost. 264 | mg_linear_allocator_reset((mg_linear_allocator *)session->decoder_allocator); 265 | session->in_end = 0; 266 | session->in_cursor = 0; 267 | int status; 268 | do { 269 | status = mg_session_read_chunk(session); 270 | } while (status == 1); 271 | return status; 272 | } 273 | -------------------------------------------------------------------------------- /src/mgsession.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2020 Memgraph Ltd. [https://memgraph.com] 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef MGCLIENT_MGSESSION_H 16 | #define MGCLIENT_MGSESSION_H 17 | 18 | #ifdef __cplusplus 19 | extern "C" { 20 | #endif 21 | 22 | #include 23 | 24 | #include "mgconstants.h" 25 | #include "mgmessage.h" 26 | #include "mgtransport.h" 27 | #include "mgvalue.h" 28 | 29 | #define MG_MAX_ERROR_SIZE 1024 30 | 31 | typedef struct mg_result { 32 | int status; 33 | mg_session *session; 34 | mg_message *message; 35 | mg_list *columns; 36 | } mg_result; 37 | 38 | typedef struct mg_session { 39 | int status; 40 | 41 | int explicit_transaction; 42 | int query_number; 43 | 44 | mg_transport *transport; 45 | 46 | int version; 47 | 48 | char *out_buffer; 49 | size_t out_begin; 50 | size_t out_end; 51 | size_t out_capacity; 52 | 53 | char *in_buffer; 54 | size_t in_end; 55 | size_t in_capacity; 56 | size_t in_cursor; 57 | 58 | mg_result result; 59 | 60 | char error_buffer[MG_MAX_ERROR_SIZE]; 61 | 62 | mg_allocator *allocator; 63 | mg_allocator *decoder_allocator; 64 | } mg_session; 65 | 66 | mg_session *mg_session_init(mg_allocator *allocator); 67 | 68 | void mg_session_invalidate(mg_session *session); 69 | 70 | void mg_session_set_error(mg_session *session, const char *fmt, ...); 71 | 72 | void mg_session_destroy(mg_session *session); 73 | 74 | int mg_session_write_raw(mg_session *session, const char *data, size_t len); 75 | 76 | int mg_session_flush_message(mg_session *session); 77 | 78 | int mg_session_write_uint8(mg_session *session, uint8_t val); 79 | 80 | int mg_session_write_uint16(mg_session *session, uint16_t val); 81 | 82 | int mg_session_write_uint32(mg_session *session, uint32_t val); 83 | 84 | int mg_session_write_uint64(mg_session *session, uint64_t val); 85 | 86 | int mg_session_write_null(mg_session *session); 87 | 88 | int mg_session_write_bool(mg_session *session, int value); 89 | 90 | int mg_session_write_integer(mg_session *session, int64_t value); 91 | 92 | int mg_session_write_float(mg_session *session, double value); 93 | 94 | int mg_session_write_string(mg_session *session, const char *str); 95 | 96 | int mg_session_write_string2(mg_session *session, uint32_t len, 97 | const char *data); 98 | 99 | int mg_session_write_list(mg_session *session, const mg_list *list); 100 | 101 | int mg_session_write_map(mg_session *session, const mg_map *map); 102 | 103 | int mg_session_write_value(mg_session *session, const mg_value *value); 104 | 105 | int mg_session_receive_message(mg_session *session); 106 | 107 | void *mg_session_allocate(mg_session *session, size_t size); 108 | 109 | int mg_session_read_integer(mg_session *session, int64_t *val); 110 | 111 | int mg_session_read_bool(mg_session *session, int *val); 112 | 113 | int mg_session_read_float(mg_session *session, double *value); 114 | 115 | int mg_session_read_string(mg_session *session, mg_string **str); 116 | 117 | int mg_session_read_list(mg_session *session, mg_list **list); 118 | 119 | int mg_session_read_map(mg_session *session, mg_map **map); 120 | 121 | int mg_session_read_node(mg_session *session, mg_node **node); 122 | 123 | int mg_session_read_relationship(mg_session *session, mg_relationship **rel); 124 | 125 | int mg_session_read_unbound_relationship(mg_session *session, 126 | mg_unbound_relationship **rel); 127 | 128 | int mg_session_read_path(mg_session *session, mg_path **path); 129 | 130 | int mg_session_read_date(mg_session *session, mg_date **date); 131 | 132 | int mg_session_read_time(mg_session *session, mg_time **time); 133 | 134 | int mg_session_read_local_time(mg_session *session, mg_local_time **local_time); 135 | 136 | int mg_session_read_date_time(mg_session *session, mg_date_time **date_time); 137 | 138 | int mg_session_read_date_time_zone_id(mg_session *session, 139 | mg_date_time_zone_id **date_time_zone_id); 140 | 141 | int mg_session_read_local_date_time(mg_session *session, 142 | mg_local_date_time **local_date_time); 143 | 144 | int mg_session_read_duration(mg_session *session, mg_duration **duration); 145 | 146 | int mg_session_read_point_2d(mg_session *session, mg_point_2d **point_2d); 147 | 148 | int mg_session_read_point_3d(mg_session *session, mg_point_3d **point_3d); 149 | 150 | int mg_session_read_value(mg_session *session, mg_value **value); 151 | 152 | int mg_session_read_bolt_message(mg_session *session, mg_message **message); 153 | 154 | // Some of these message types are never sent by client, but send functions are 155 | // still here for testing. 156 | int mg_session_send_init_message(mg_session *session, const char *client_name, 157 | const mg_map *auth_token); 158 | 159 | int mg_session_send_hello_message(mg_session *session, const mg_map *extra); 160 | 161 | int mg_session_send_run_message(mg_session *session, const char *statement, 162 | const mg_map *parameters, const mg_map *extra); 163 | 164 | int mg_session_send_pull_message(mg_session *session, const mg_map *extra); 165 | 166 | int mg_session_send_reset_message(mg_session *session); 167 | 168 | int mg_session_send_ack_failure_message(mg_session *session); 169 | 170 | int mg_session_send_failure_message(mg_session *session, 171 | const mg_map *metadata); 172 | 173 | int mg_session_send_success_message(mg_session *session, 174 | const mg_map *metadata); 175 | 176 | int mg_session_send_record_message(mg_session *session, const mg_list *fields); 177 | 178 | int mg_session_send_begin_message(mg_session *session, const mg_map *extra); 179 | 180 | int mg_session_send_commit_messsage(mg_session *session); 181 | 182 | int mg_session_send_rollback_messsage(mg_session *session); 183 | 184 | #ifdef __cplusplus 185 | } 186 | #endif 187 | 188 | #endif /* MGCLIENT_MGSESSION_H */ 189 | -------------------------------------------------------------------------------- /src/mgsocket.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2020 Memgraph Ltd. [https://memgraph.com] 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef MGCLIENT_MGSOCKET_H 16 | #define MGCLIENT_MGSOCKET_H 17 | 18 | #ifdef __cplusplus 19 | extern "C" { 20 | #endif 21 | 22 | #ifdef MGCLIENT_ON_APPLE 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #endif // MGCLIENT_ON_APPLE 30 | 31 | #ifdef MGCLIENT_ON_LINUX 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #endif // MGCLIENT_ON_LINUX 42 | 43 | #ifdef MGCLIENT_ON_WINDOWS 44 | #include 45 | #include 46 | #include 47 | #ifdef _MSC_VER 48 | typedef long ssize_t; 49 | #endif 50 | #endif // MGCLIENT_ON_WINDOWS 51 | 52 | #include "mgclient.h" 53 | #include "mgsession.h" 54 | 55 | /// Initializes underlying resources. Has to be called at the beginning of a 56 | /// process using socket resources. 57 | int mg_socket_init(void); 58 | 59 | /// Returns a descriptor referencing the new socket or MG_ERROR_SOCKET in the 60 | /// case of any failure. 61 | int mg_socket_create(int af, int type, int protocol); 62 | 63 | /// Checks for errors after \ref mg_socket_create call. 64 | /// 65 | /// \param[in] sock Return value out of \ref mg_socket_create call. 66 | /// \param[in] session A pointer to the session object to set the error 67 | /// message if required. 68 | /// 69 | /// \return \ref MG_ERROR in case of an error or \ref MG_SUCCESS if there is no 70 | /// error. In the error case, session will have the underlying error message 71 | /// set. 72 | int mg_socket_create_handle_error(int sock, mg_session *session); 73 | 74 | /// Connects the socket referred to by the sock descriptor to the address 75 | /// specified by addr. The addrlen argument specifies the size of addr. 76 | /// 77 | /// \return \ref MG_ERROR in case of an error or \ref MG_SUCCESS if there is no 78 | /// error. 79 | int mg_socket_connect(int sock, const struct sockaddr *addr, socklen_t addrlen); 80 | 81 | /// Checks for errors after \ref mg_socket_connect call. 82 | /// 83 | /// \param[out] sock Return value out of \ref mg_socket_create call. 84 | /// \param[in] status Return value out of \ref mg_socket_connect call. 85 | /// \param[in] session A pointer to the session object to set the error 86 | /// message if required. 87 | /// 88 | /// \return \ref MG_ERROR in case of an error or \ref MG_SUCCESS if there is no 89 | /// error. In the error case, session will have the underlying error message 90 | /// set + value referenced by the sock will be set to MG_ERROR_SOCKET. 91 | int mg_socket_connect_handle_error(int *sock, int status, mg_session *session); 92 | 93 | /// Sets options for a socket referenced with the given sock descriptor. 94 | int mg_socket_options(int sock, mg_session *session); 95 | 96 | /// Sends len bytes from buf to the socket referenced by the sock descriptor. 97 | ssize_t mg_socket_send(int sock, const void *buf, int len); 98 | 99 | /// Reads len bytes to buf from the socket referenced by the sock descriptor. 100 | ssize_t mg_socket_receive(int sock, void *buf, int len); 101 | 102 | /// Waits for one of a set of file descriptors to become ready to perform I/O. 103 | int mg_socket_poll(struct pollfd *fds, unsigned int nfds, int timeout); 104 | 105 | /// Creates a socket pair. 106 | int mg_socket_pair(int d, int type, int protocol, int *sv); 107 | 108 | /// Closes the socket referenced by the sock descriptor. 109 | int mg_socket_close(int sock); 110 | 111 | /// Used to get a native error message after some socket call fails. 112 | /// Has to be called immediately after the failed socket function. 113 | char *mg_socket_error(void); 114 | 115 | /// Should be called at the end of any process which previously called the 116 | /// \ref mg_socket_init function. 117 | void mg_socket_finalize(void); 118 | 119 | #ifdef __cplusplus 120 | } 121 | #endif 122 | 123 | #endif /* MGCLIENT_MGSOCKET_H */ 124 | -------------------------------------------------------------------------------- /src/mgtransport.c: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2020 Memgraph Ltd. [https://memgraph.com] 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "mgtransport.h" 16 | 17 | #include 18 | #include 19 | #include 20 | #ifdef MGCLIENT_ON_LINUX 21 | #ifndef __EMSCRIPTEN__ 22 | #include 23 | #endif 24 | #endif // MGCLIENT_ON_LINUX 25 | 26 | #include "mgallocator.h" 27 | #include "mgclient.h" 28 | #include "mgsocket.h" 29 | #ifdef __EMSCRIPTEN__ 30 | #include "mgwasm.h" 31 | #endif 32 | 33 | int mg_init_ssl = 1; 34 | 35 | int mg_transport_send(mg_transport *transport, const char *buf, size_t len) { 36 | return transport->send(transport, buf, len); 37 | } 38 | 39 | int mg_transport_recv(mg_transport *transport, char *buf, size_t len) { 40 | return transport->recv(transport, buf, len); 41 | } 42 | 43 | void mg_transport_destroy(mg_transport *transport) { 44 | transport->destroy(transport); 45 | } 46 | 47 | void mg_transport_suspend_until_ready_to_read(struct mg_transport *transport) { 48 | if (transport->suspend_until_ready_to_read) { 49 | transport->suspend_until_ready_to_read(transport); 50 | } 51 | } 52 | 53 | void mg_transport_suspend_until_ready_to_write(struct mg_transport *transport) { 54 | if (transport->suspend_until_ready_to_write) { 55 | transport->suspend_until_ready_to_write(transport); 56 | } 57 | } 58 | 59 | int mg_raw_transport_init(int sockfd, mg_raw_transport **transport, 60 | mg_allocator *allocator) { 61 | mg_raw_transport *ttransport = 62 | mg_allocator_malloc(allocator, sizeof(mg_raw_transport)); 63 | if (!ttransport) { 64 | return MG_ERROR_OOM; 65 | } 66 | ttransport->sockfd = sockfd; 67 | ttransport->send = mg_raw_transport_send; 68 | ttransport->recv = mg_raw_transport_recv; 69 | ttransport->destroy = mg_raw_transport_destroy; 70 | ttransport->suspend_until_ready_to_read = 71 | mg_raw_transport_suspend_until_ready_to_read; 72 | ttransport->suspend_until_ready_to_write = 73 | mg_raw_transport_suspend_until_ready_to_write; 74 | ttransport->allocator = allocator; 75 | *transport = ttransport; 76 | return 0; 77 | } 78 | 79 | int mg_raw_transport_send(struct mg_transport *transport, const char *buf, 80 | size_t len) { 81 | int sockfd = ((mg_raw_transport *)transport)->sockfd; 82 | size_t total_sent = 0; 83 | while (total_sent < len) { 84 | // TODO(mtomic): maybe enable using MSG_MORE here 85 | ssize_t sent_now = 86 | mg_socket_send(sockfd, buf + total_sent, len - total_sent); 87 | if (sent_now == -1) { 88 | perror("mg_raw_transport_send"); 89 | return -1; 90 | } 91 | total_sent += (size_t)sent_now; 92 | } 93 | return 0; 94 | } 95 | 96 | int mg_raw_transport_recv(struct mg_transport *transport, char *buf, 97 | size_t len) { 98 | int sockfd = ((mg_raw_transport *)transport)->sockfd; 99 | size_t total_received = 0; 100 | while (total_received < len) { 101 | ssize_t received_now = 102 | mg_socket_receive(sockfd, buf + total_received, len - total_received); 103 | if (received_now == 0) { 104 | // Server closed the connection. 105 | fprintf(stderr, "mg_raw_transport_recv: connection closed by server\n"); 106 | return -1; 107 | } 108 | if (received_now == -1) { 109 | perror("mg_raw_transport_recv"); 110 | return -1; 111 | } 112 | total_received += (size_t)received_now; 113 | } 114 | return 0; 115 | } 116 | 117 | void mg_raw_transport_destroy(struct mg_transport *transport) { 118 | mg_raw_transport *self = (mg_raw_transport *)transport; 119 | if (mg_socket_close(self->sockfd) != 0) { 120 | abort(); 121 | } 122 | mg_allocator_free(self->allocator, transport); 123 | } 124 | 125 | void mg_raw_transport_suspend_until_ready_to_read( 126 | struct mg_transport *transport) { 127 | #ifdef __EMSCRIPTEN__ 128 | const int sock = ((mg_raw_transport *)transport)->sockfd; 129 | mg_wasm_suspend_until_ready_to_read(sock); 130 | #else 131 | (void)transport; 132 | #endif 133 | } 134 | 135 | void mg_raw_transport_suspend_until_ready_to_write( 136 | struct mg_transport *transport) { 137 | #ifdef __EMSCRIPTEN__ 138 | const int sock = ((mg_raw_transport *)transport)->sockfd; 139 | mg_wasm_suspend_until_ready_to_write(sock); 140 | #else 141 | (void)transport; 142 | #endif 143 | } 144 | 145 | #ifndef __EMSCRIPTEN__ 146 | static int print_ssl_error(const char *str, size_t len, void *u) { 147 | (void)len; 148 | fprintf(stderr, "%s: %s", (char *)u, str); 149 | return 0; 150 | } 151 | 152 | static char *hex_encode(unsigned char *data, unsigned int len, 153 | mg_allocator *allocator) { 154 | char *encoded = mg_allocator_malloc(allocator, 2 * len + 1); 155 | for (unsigned int i = 0; i < len; ++i) { 156 | sprintf(encoded + 2 * i, "%02x", data[i]); 157 | } 158 | return encoded; 159 | } 160 | 161 | static void mg_openssl_init(void) { 162 | #if OPENSSL_VERSION_NUMBER < 0x10100000L 163 | static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; 164 | static int mg_ssl_initialized = 0; 165 | pthread_mutex_lock(&mutex); 166 | if (mg_init_ssl && !mg_ssl_initialized) { 167 | printf("initializing openssl\n"); 168 | SSL_library_init(); 169 | SSL_load_error_strings(); 170 | ERR_load_crypto_strings(); 171 | mg_ssl_initialized = 1; 172 | } 173 | pthread_mutex_unlock(&mutex); 174 | #endif 175 | } 176 | 177 | int mg_secure_transport_init(int sockfd, const char *cert_file, 178 | const char *key_file, 179 | mg_secure_transport **transport, 180 | mg_allocator *allocator) { 181 | mg_openssl_init(); 182 | 183 | SSL_CTX *ctx = NULL; 184 | SSL *ssl = NULL; 185 | BIO *bio = NULL; 186 | 187 | int status = 0; 188 | 189 | ERR_clear_error(); 190 | 191 | #if OPENSSL_VERSION_NUMBER < 0x10100000L 192 | ctx = SSL_CTX_new(SSLv23_client_method()); 193 | #else 194 | ctx = SSL_CTX_new(TLS_client_method()); 195 | #endif 196 | 197 | if (!ctx) { 198 | status = MG_ERROR_SSL_ERROR; 199 | goto failure; 200 | } 201 | 202 | if (cert_file && key_file) { 203 | if (SSL_CTX_use_certificate_chain_file(ctx, cert_file) != 1 || 204 | SSL_CTX_use_PrivateKey_file(ctx, key_file, SSL_FILETYPE_PEM) != 1) { 205 | status = MG_ERROR_SSL_ERROR; 206 | goto failure; 207 | } 208 | } 209 | 210 | SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv3); 211 | ssl = SSL_new(ctx); 212 | 213 | if (!ssl) { 214 | status = MG_ERROR_SSL_ERROR; 215 | goto failure; 216 | } 217 | 218 | // SSL_CTX object is reference counted, we're destroying this local reference, 219 | // but reference from SSL object stays. 220 | SSL_CTX_free(ctx); 221 | ctx = NULL; 222 | 223 | bio = BIO_new_socket(sockfd, BIO_NOCLOSE); 224 | if (!bio) { 225 | status = MG_ERROR_SSL_ERROR; 226 | goto failure; 227 | } 228 | SSL_set_bio(ssl, bio, bio); 229 | 230 | int ret = SSL_connect(ssl); 231 | if (ret < 0) { 232 | status = MG_ERROR_SSL_ERROR; 233 | goto failure; 234 | } 235 | 236 | // Get server's public key type and fingerprint. 237 | X509 *peer_cert = SSL_get_peer_certificate(ssl); 238 | assert(peer_cert); 239 | EVP_PKEY *peer_pubkey = X509_get_pubkey(peer_cert); 240 | int nid = EVP_PKEY_base_id(peer_pubkey); 241 | EVP_PKEY_free(peer_pubkey); 242 | const char *peer_pubkey_type = 243 | (nid == NID_undef) ? "UNKNOWN" : OBJ_nid2ln(nid); 244 | unsigned char peer_pubkey_fp[EVP_MAX_MD_SIZE]; 245 | unsigned int peer_pubkey_fp_len; 246 | if (X509_pubkey_digest(peer_cert, EVP_sha512(), peer_pubkey_fp, 247 | &peer_pubkey_fp_len) != 1) { 248 | status = MG_ERROR_SSL_ERROR; 249 | X509_free(peer_cert); 250 | goto failure; 251 | } 252 | X509_free(peer_cert); 253 | 254 | mg_secure_transport *ttransport = 255 | mg_allocator_malloc(allocator, sizeof(mg_secure_transport)); 256 | if (!ttransport) { 257 | status = MG_ERROR_OOM; 258 | goto failure; 259 | } 260 | 261 | // Take ownership of the socket now that everything went well. 262 | BIO_set_close(bio, BIO_CLOSE); 263 | 264 | ttransport->ssl = ssl; 265 | ttransport->bio = bio; 266 | ttransport->peer_pubkey_type = peer_pubkey_type; 267 | ttransport->peer_pubkey_fp = 268 | hex_encode(peer_pubkey_fp, peer_pubkey_fp_len, allocator); 269 | ttransport->send = mg_secure_transport_send; 270 | ttransport->recv = mg_secure_transport_recv; 271 | ttransport->suspend_until_ready_to_read = NULL; 272 | ttransport->suspend_until_ready_to_write = NULL; 273 | ttransport->destroy = mg_secure_transport_destroy; 274 | ttransport->allocator = allocator; 275 | *transport = ttransport; 276 | 277 | return 0; 278 | 279 | failure: 280 | if (status == MG_ERROR_SSL_ERROR) { 281 | ERR_print_errors_cb(print_ssl_error, "mg_secure_transport_init"); 282 | } 283 | SSL_CTX_free(ctx); 284 | if (ssl) { 285 | // If SSL object was successfuly created, it owns the BIO so we don't need 286 | // to destroy it. 287 | SSL_free(ssl); 288 | } else { 289 | BIO_free(bio); 290 | } 291 | 292 | return status; 293 | } 294 | 295 | int mg_secure_transport_send(mg_transport *transport, const char *buf, 296 | size_t len) { 297 | SSL *ssl = ((mg_secure_transport *)transport)->ssl; 298 | BIO *bio = ((mg_secure_transport *)transport)->bio; 299 | size_t total_sent = 0; 300 | while (total_sent < len) { 301 | ERR_clear_error(); 302 | int sent_now = SSL_write(ssl, buf + total_sent, (int)(len - total_sent)); 303 | if (sent_now <= 0) { 304 | int err = SSL_get_error(ssl, sent_now); 305 | if (err == SSL_ERROR_WANT_READ) { 306 | struct pollfd p; 307 | if (BIO_get_fd(bio, &p.fd) < 0) { 308 | abort(); 309 | } 310 | p.events = POLLIN; 311 | if (mg_socket_poll(&p, 1, -1) < 0) { 312 | return -1; 313 | } 314 | continue; 315 | } else { 316 | ERR_print_errors_cb(print_ssl_error, "mg_secure_transport_send"); 317 | return -1; 318 | } 319 | } 320 | assert((size_t)sent_now == len); 321 | total_sent += (size_t)sent_now; 322 | } 323 | return 0; 324 | } 325 | 326 | int mg_secure_transport_recv(mg_transport *transport, char *buf, size_t len) { 327 | SSL *ssl = ((mg_secure_transport *)transport)->ssl; 328 | BIO *bio = ((mg_secure_transport *)transport)->bio; 329 | size_t total_received = 0; 330 | while (total_received < len) { 331 | ERR_clear_error(); 332 | int received_now = 333 | SSL_read(ssl, buf + total_received, (int)(len - total_received)); 334 | if (received_now <= 0) { 335 | int err = SSL_get_error(ssl, received_now); 336 | if (err == SSL_ERROR_WANT_READ) { 337 | struct pollfd p; 338 | if (BIO_get_fd(bio, &p.fd) < 0) { 339 | abort(); 340 | } 341 | p.events = POLLIN; 342 | if (mg_socket_poll(&p, 1, -1) < 0) { 343 | return -1; 344 | } 345 | continue; 346 | } else { 347 | ERR_print_errors_cb(print_ssl_error, "mg_secure_transport_recv"); 348 | return -1; 349 | } 350 | } 351 | total_received += (size_t)received_now; 352 | } 353 | return 0; 354 | } 355 | 356 | void mg_secure_transport_destroy(mg_transport *transport) { 357 | mg_secure_transport *self = (mg_secure_transport *)transport; 358 | SSL_free(self->ssl); 359 | self->bio = NULL; 360 | self->ssl = NULL; 361 | mg_allocator_free(self->allocator, self->peer_pubkey_fp); 362 | mg_allocator_free(self->allocator, self); 363 | } 364 | #endif 365 | -------------------------------------------------------------------------------- /src/mgtransport.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2020 Memgraph Ltd. [https://memgraph.com] 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef MGCLIENT_MGTRANSPORT_H 16 | #define MGCLIENT_MGTRANSPORT_H 17 | 18 | #ifdef __cplusplus 19 | extern "C" { 20 | #endif 21 | 22 | #include 23 | #include 24 | 25 | #ifndef __EMSCRIPTEN__ 26 | #include 27 | #include 28 | #include 29 | #endif 30 | 31 | #include "mgallocator.h" 32 | #include "mgcommon.h" 33 | 34 | typedef struct mg_transport { 35 | int (*send)(struct mg_transport *, const char *buf, size_t len); 36 | int (*recv)(struct mg_transport *, char *buf, size_t len); 37 | void (*destroy)(struct mg_transport *); 38 | void (*suspend_until_ready_to_read)(struct mg_transport *); 39 | void (*suspend_until_ready_to_write)(struct mg_transport *); 40 | } mg_transport; 41 | 42 | typedef struct mg_raw_transport { 43 | int (*send)(struct mg_transport *, const char *buf, size_t len); 44 | int (*recv)(struct mg_transport *, char *buf, size_t len); 45 | void (*destroy)(struct mg_transport *); 46 | void (*suspend_until_ready_to_read)(struct mg_transport *); 47 | void (*suspend_until_ready_to_write)(struct mg_transport *); 48 | int sockfd; 49 | mg_allocator *allocator; 50 | } mg_raw_transport; 51 | 52 | #ifndef __EMSCRIPTEN__ 53 | typedef struct mg_secure_transport { 54 | int (*send)(struct mg_transport *, const char *buf, size_t len); 55 | int (*recv)(struct mg_transport *, char *buf, size_t len); 56 | void (*destroy)(struct mg_transport *); 57 | void (*suspend_until_ready_to_read)(struct mg_transport *); 58 | void (*suspend_until_ready_to_write)(struct mg_transport *); 59 | SSL *ssl; 60 | BIO *bio; 61 | const char *peer_pubkey_type; 62 | char *peer_pubkey_fp; 63 | mg_allocator *allocator; 64 | } mg_secure_transport; 65 | #endif 66 | 67 | int mg_transport_send(mg_transport *transport, const char *buf, size_t len); 68 | 69 | int mg_transport_recv(mg_transport *transport, char *buf, size_t len); 70 | 71 | void mg_transport_destroy(mg_transport *transport); 72 | 73 | void mg_transport_suspend_until_ready_to_read(struct mg_transport *); 74 | 75 | void mg_transport_suspend_until_ready_to_write(struct mg_transport *); 76 | 77 | int mg_raw_transport_init(int sockfd, mg_raw_transport **transport, 78 | mg_allocator *allocator); 79 | 80 | int mg_raw_transport_send(struct mg_transport *, const char *buf, size_t len); 81 | 82 | int mg_raw_transport_recv(struct mg_transport *, char *buf, size_t len); 83 | 84 | void mg_raw_transport_destroy(struct mg_transport *); 85 | 86 | void mg_raw_transport_suspend_until_ready_to_read(struct mg_transport *); 87 | 88 | void mg_raw_transport_suspend_until_ready_to_write(struct mg_transport *); 89 | 90 | #ifndef __EMSCRIPTEN__ 91 | // This function is mocked in tests during linking by using --wrap. ON_APPLE 92 | // there is no --wrap. An alternative is to use -alias but if a symbol is 93 | // strong linking fails. 94 | MG_ATTRIBUTE_WEAK int mg_secure_transport_init(int sockfd, 95 | const char *cert_file, 96 | const char *key_file, 97 | mg_secure_transport **transport, 98 | mg_allocator *allocator); 99 | 100 | int mg_secure_transport_send(mg_transport *, const char *buf, size_t len); 101 | 102 | int mg_secure_transport_recv(mg_transport *, char *buf, size_t len); 103 | 104 | void mg_secure_transport_destroy(mg_transport *); 105 | #endif 106 | 107 | #ifdef __cplusplus 108 | } 109 | #endif 110 | 111 | #endif /* MGCLIENT_MGTRANSPORT_H */ 112 | -------------------------------------------------------------------------------- /src/mgvalue.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2020 Memgraph Ltd. [https://memgraph.com] 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef MGCLIENT_MGVALUE_H 16 | #define MGCLIENT_MGVALUE_H 17 | 18 | #ifdef __cplusplus 19 | extern "C" { 20 | #endif 21 | 22 | #include "mgallocator.h" 23 | #include "mgclient.h" 24 | 25 | typedef struct mg_value mg_value; 26 | 27 | typedef struct mg_string { 28 | uint32_t size; 29 | char *data; 30 | } mg_string; 31 | 32 | typedef struct mg_list { 33 | uint32_t size; 34 | uint32_t capacity; 35 | mg_value **elements; 36 | } mg_list; 37 | 38 | typedef struct mg_map { 39 | uint32_t size; 40 | uint32_t capacity; 41 | mg_string **keys; 42 | mg_value **values; 43 | } mg_map; 44 | 45 | typedef struct mg_node { 46 | int64_t id; 47 | uint32_t label_count; 48 | mg_string **labels; 49 | mg_map *properties; 50 | } mg_node; 51 | 52 | typedef struct mg_relationship { 53 | int64_t id; 54 | int64_t start_id; 55 | int64_t end_id; 56 | mg_string *type; 57 | mg_map *properties; 58 | } mg_relationship; 59 | 60 | typedef struct mg_unbound_relationship { 61 | int64_t id; 62 | mg_string *type; 63 | mg_map *properties; 64 | } mg_unbound_relationship; 65 | 66 | typedef struct mg_path { 67 | uint32_t node_count; 68 | uint32_t relationship_count; 69 | uint32_t sequence_length; 70 | mg_node **nodes; 71 | mg_unbound_relationship **relationships; 72 | int64_t *sequence; 73 | } mg_path; 74 | 75 | typedef struct mg_date { 76 | int64_t days; 77 | } mg_date; 78 | 79 | typedef struct mg_time { 80 | int64_t nanoseconds; 81 | int64_t tz_offset_seconds; 82 | } mg_time; 83 | 84 | typedef struct mg_local_time { 85 | int64_t nanoseconds; 86 | } mg_local_time; 87 | 88 | typedef struct mg_date_time { 89 | int64_t seconds; 90 | int64_t nanoseconds; 91 | int64_t tz_offset_minutes; 92 | } mg_date_time; 93 | 94 | typedef struct mg_date_time_zone_id { 95 | int64_t seconds; 96 | int64_t nanoseconds; 97 | int64_t tz_id; 98 | } mg_date_time_zone_id; 99 | 100 | typedef struct mg_local_date_time { 101 | int64_t seconds; 102 | int64_t nanoseconds; 103 | } mg_local_date_time; 104 | 105 | typedef struct mg_duration { 106 | int64_t months; 107 | int64_t days; 108 | int64_t seconds; 109 | int64_t nanoseconds; 110 | } mg_duration; 111 | 112 | typedef struct mg_point_2d { 113 | int64_t srid; 114 | double x; 115 | double y; 116 | } mg_point_2d; 117 | 118 | typedef struct mg_point_3d { 119 | int64_t srid; 120 | double x; 121 | double y; 122 | double z; 123 | } mg_point_3d; 124 | 125 | struct mg_value { 126 | enum mg_value_type type; 127 | union { 128 | int bool_v; 129 | int64_t integer_v; 130 | double float_v; 131 | mg_string *string_v; 132 | mg_list *list_v; 133 | mg_map *map_v; 134 | mg_node *node_v; 135 | mg_relationship *relationship_v; 136 | mg_unbound_relationship *unbound_relationship_v; 137 | mg_path *path_v; 138 | mg_date *date_v; 139 | mg_time *time_v; 140 | mg_local_time *local_time_v; 141 | mg_date_time *date_time_v; 142 | mg_date_time_zone_id *date_time_zone_id_v; 143 | mg_local_date_time *local_date_time_v; 144 | mg_duration *duration_v; 145 | mg_point_2d *point_2d_v; 146 | mg_point_3d *point_3d_v; 147 | }; 148 | }; 149 | 150 | mg_string *mg_string_alloc(uint32_t size, mg_allocator *allocator); 151 | 152 | mg_list *mg_list_alloc(uint32_t size, mg_allocator *allocator); 153 | 154 | mg_map *mg_map_alloc(uint32_t size, mg_allocator *allocator); 155 | 156 | mg_node *mg_node_alloc(uint32_t label_count, mg_allocator *allocator); 157 | 158 | mg_path *mg_path_alloc(uint32_t node_count, uint32_t relationship_count, 159 | uint32_t sequence_length, mg_allocator *allocator); 160 | 161 | mg_date *mg_date_alloc(mg_allocator *allocator); 162 | 163 | mg_time *mg_time_alloc(mg_allocator *allocator); 164 | 165 | mg_local_time *mg_local_time_alloc(mg_allocator *allocator); 166 | 167 | mg_date_time *mg_date_time_alloc(mg_allocator *allocator); 168 | 169 | mg_date_time_zone_id *mg_date_time_zone_id_alloc(mg_allocator *allocator); 170 | 171 | mg_local_date_time *mg_local_date_time_alloc(mg_allocator *allocator); 172 | 173 | mg_duration *mg_duration_alloc(mg_allocator *allocator); 174 | 175 | mg_point_2d *mg_point_2d_alloc(mg_allocator *allocator); 176 | 177 | mg_point_3d *mg_point_3d_alloc(mg_allocator *allocator); 178 | 179 | mg_node *mg_node_make(int64_t id, uint32_t label_count, mg_string **labels, 180 | mg_map *properties); 181 | 182 | mg_relationship *mg_relationship_make(int64_t id, int64_t start_id, 183 | int64_t end_id, mg_string *type, 184 | mg_map *properties); 185 | 186 | mg_unbound_relationship *mg_unbound_relationship_make(int64_t id, 187 | mg_string *type, 188 | mg_map *properties); 189 | 190 | mg_path *mg_path_make(uint32_t node_count, mg_node **nodes, 191 | uint32_t relationship_count, 192 | mg_unbound_relationship **relationships, 193 | uint32_t sequence_length, const int64_t *const sequence); 194 | 195 | mg_value *mg_value_copy_ca(const mg_value *val, mg_allocator *allocator); 196 | 197 | mg_string *mg_string_copy_ca(const mg_string *string, mg_allocator *allocator); 198 | 199 | mg_list *mg_list_copy_ca(const mg_list *list, mg_allocator *allocator); 200 | 201 | mg_map *mg_map_copy_ca(const mg_map *map, mg_allocator *allocator); 202 | 203 | mg_node *mg_node_copy_ca(const mg_node *node, mg_allocator *allocator); 204 | 205 | mg_relationship *mg_relationship_copy_ca(const mg_relationship *rel, 206 | mg_allocator *allocator); 207 | 208 | mg_unbound_relationship *mg_unbound_relationship_copy_ca( 209 | const mg_unbound_relationship *rel, mg_allocator *allocator); 210 | 211 | mg_path *mg_path_copy_ca(const mg_path *path, mg_allocator *allocator); 212 | 213 | mg_date *mg_date_copy_ca(const mg_date *date, mg_allocator *allocator); 214 | 215 | mg_time *mg_time_copy_ca(const mg_time *time, mg_allocator *allocator); 216 | 217 | mg_local_time *mg_local_time_copy_ca(const mg_local_time *local_time, 218 | mg_allocator *allocator); 219 | 220 | mg_date_time *mg_date_time_copy_ca(const mg_date_time *date_time, 221 | mg_allocator *allocator); 222 | 223 | mg_date_time_zone_id *mg_date_time_zone_id_copy_ca( 224 | const mg_date_time_zone_id *date_time_zone_id, mg_allocator *allocator); 225 | 226 | mg_local_date_time *mg_local_date_time_copy_ca( 227 | const mg_local_date_time *local_date_time, mg_allocator *allocator); 228 | 229 | mg_duration *mg_duration_copy_ca(const mg_duration *duration, 230 | mg_allocator *allocator); 231 | 232 | mg_point_2d *mg_point_2d_copy_ca(const mg_point_2d *point_2d, 233 | mg_allocator *allocator); 234 | 235 | mg_point_3d *mg_point_3d_copy_ca(const mg_point_3d *point_3d, 236 | mg_allocator *allocator); 237 | 238 | void mg_path_destroy_ca(mg_path *path, mg_allocator *allocator); 239 | 240 | void mg_value_destroy_ca(mg_value *val, mg_allocator *allocator); 241 | 242 | void mg_string_destroy_ca(mg_string *string, mg_allocator *allocator); 243 | 244 | void mg_list_destroy_ca(mg_list *list, mg_allocator *allocator); 245 | 246 | void mg_map_destroy_ca(mg_map *map, mg_allocator *allocator); 247 | 248 | void mg_node_destroy_ca(mg_node *node, mg_allocator *allocator); 249 | 250 | void mg_relationship_destroy_ca(mg_relationship *rel, mg_allocator *allocator); 251 | 252 | void mg_unbound_relationship_destroy_ca(mg_unbound_relationship *rel, 253 | mg_allocator *allocator); 254 | 255 | void mg_path_destroy_ca(mg_path *path, mg_allocator *allocator); 256 | 257 | void mg_date_destroy_ca(mg_date *date, mg_allocator *allocator); 258 | 259 | void mg_time_destroy_ca(mg_time *time, mg_allocator *allocator); 260 | 261 | void mg_local_time_destroy_ca(mg_local_time *local_time, 262 | mg_allocator *allocator); 263 | 264 | void mg_date_time_destroy_ca(mg_date_time *date_time, mg_allocator *allocator); 265 | 266 | void mg_date_time_zone_id_destroy_ca(mg_date_time_zone_id *date_time_zone_id, 267 | mg_allocator *allocator); 268 | 269 | void mg_local_date_time_destroy_ca(mg_local_date_time *local_date_time, 270 | mg_allocator *allocator); 271 | 272 | void mg_duration_destroy_ca(mg_duration *duration, mg_allocator *allocator); 273 | 274 | void mg_point_2d_destroy_ca(mg_point_2d *point_2d, mg_allocator *allocator); 275 | 276 | void mg_point_3d_destroy_ca(mg_point_3d *point_3d, mg_allocator *allocator); 277 | 278 | int mg_string_equal(const mg_string *lhs, const mg_string *rhs); 279 | 280 | int mg_map_equal(const mg_map *lhs, const mg_map *rhs); 281 | 282 | int mg_node_equal(const mg_node *lhs, const mg_node *rhs); 283 | 284 | int mg_relationship_equal(const mg_relationship *lhs, 285 | const mg_relationship *rhs); 286 | 287 | int mg_unbound_relationship_equal(const mg_unbound_relationship *lhs, 288 | const mg_unbound_relationship *rhs); 289 | 290 | int mg_path_equal(const mg_path *lhs, const mg_path *rhs); 291 | 292 | int mg_value_equal(const mg_value *lhs, const mg_value *rhs); 293 | 294 | extern mg_map *mg_default_pull_extra_map; 295 | 296 | extern mg_map mg_empty_map; 297 | 298 | #ifdef __cplusplus 299 | } 300 | #endif 301 | 302 | #endif /* MGCLIENT_MGVALUE_H */ 303 | -------------------------------------------------------------------------------- /src/mgwasm.c: -------------------------------------------------------------------------------- 1 | #include "mgwasm.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "emscripten.h" 8 | 9 | int read_loop(const int sock) { 10 | fd_set fdr; 11 | FD_ZERO(&fdr); 12 | FD_SET(sock, &fdr); 13 | int poll = select(sock + 1, &fdr, NULL, NULL, NULL); 14 | if (poll == -1) { 15 | return -1; 16 | } 17 | if (!FD_ISSET(sock, &fdr)) { 18 | return -100; 19 | } 20 | return 1; 21 | } 22 | 23 | int write_loop(const int sock) { 24 | fd_set fdw; 25 | FD_ZERO(&fdw); 26 | FD_SET(sock, &fdw); 27 | const int poll = select(sock + 1, NULL, &fdw, NULL, NULL); 28 | if (poll == -1) { 29 | return -1; 30 | } 31 | int result; 32 | socklen_t result_len = sizeof(result); 33 | if (getsockopt(sock, SOL_SOCKET, SO_ERROR, &result, &result_len) < 0) { 34 | return -1; 35 | } 36 | 37 | if (result != 0) { 38 | return -1; 39 | } 40 | 41 | if (!FD_ISSET(sock, &fdw)) { 42 | return -100; 43 | } 44 | return 1; 45 | } 46 | 47 | static const size_t DELAY_MS = 10; 48 | 49 | int mg_wasm_suspend_until_ready_to_read(const int sock) { 50 | while (1) { 51 | const int res = read_loop(sock); 52 | if (res == 1 || res == -1) { 53 | return res; 54 | } 55 | emscripten_sleep(DELAY_MS); 56 | } 57 | } 58 | 59 | int mg_wasm_suspend_until_ready_to_write(const int sock) { 60 | while (1) { 61 | const int res = write_loop(sock); 62 | if (res == 1 || res == -1) { 63 | return res; 64 | } 65 | if (res == -1) { 66 | return -1; 67 | } 68 | emscripten_sleep(DELAY_MS); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/mgwasm.h: -------------------------------------------------------------------------------- 1 | #ifndef MGCLIENT_MGWASM_H 2 | #define MGCLIENT_MGWASM_H 3 | 4 | int mg_wasm_suspend_until_ready_to_read(int sock); 5 | int mg_wasm_suspend_until_ready_to_write(int sock); 6 | 7 | #endif /* MGCLIENT_MGWASM_H */ 8 | -------------------------------------------------------------------------------- /src/windows/mgcommon.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2020 Memgraph Ltd. [https://memgraph.com] 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef MGCLIENT_WINDOWS_MGCOMMON_H 16 | #define MGCLIENT_WINDOWS_MGCOMMON_H 17 | 18 | // Based on https://gist.github.com/PkmX/63dd23f28ba885be53a5 19 | 20 | #if defined(_MSC_VER) 21 | 22 | #include 23 | 24 | #define htobe16(x) _byteswap_ushort(x) 25 | #define htole16(x) (x) 26 | #define be16toh(x) _byteswap_ushort(x) 27 | #define le16toh(x) (x) 28 | 29 | #define htobe32(x) _byteswap_ulong(x) 30 | #define htole32(x) (x) 31 | #define be32toh(x) _byteswap_ulong(x) 32 | #define le32toh(x) (x) 33 | 34 | #define htobe64(x) _byteswap_uint64(x) 35 | #define htole64(x) (x) 36 | #define be64toh(x) _byteswap_uint64(x) 37 | #define le64toh(x) (x) 38 | 39 | #else 40 | 41 | #define htobe16(x) __builtin_bswap16(x) 42 | #define htole16(x) (x) 43 | #define be16toh(x) __builtin_bswap16(x) 44 | #define le16toh(x) (x) 45 | 46 | #define htobe32(x) __builtin_bswap32(x) 47 | #define htole32(x) (x) 48 | #define be32toh(x) __builtin_bswap32(x) 49 | #define le32toh(x) (x) 50 | 51 | #define htobe64(x) __builtin_bswap64(x) 52 | #define htole64(x) (x) 53 | #define be64toh(x) __builtin_bswap64(x) 54 | #define le64toh(x) (x) 55 | 56 | #endif 57 | 58 | #endif 59 | -------------------------------------------------------------------------------- /src/windows/mgsocket.c: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2020 Memgraph Ltd. [https://memgraph.com] 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "mgsocket.h" 16 | 17 | #include 18 | 19 | // Please refer to https://docs.microsoft.com/en-us/windows/win32/api/winsock2/ 20 | // for more details about Windows system calls. 21 | 22 | int mg_socket_init() { 23 | WSADATA data; 24 | int status = WSAStartup(MAKEWORD(2, 2), &data); 25 | if (status != 0) { 26 | fprintf(stderr, "WSAStartup failed: %s\n", mg_socket_error()); 27 | abort(); 28 | } 29 | return MG_SUCCESS; 30 | } 31 | 32 | int mg_socket_create(int af, int type, int protocol) { 33 | SOCKET sock = socket(af, type, protocol); 34 | // Useful info here https://stackoverflow.com/questions/10817252. 35 | if (sock == INVALID_SOCKET) { 36 | return MG_ERROR_SOCKET; 37 | } 38 | if (sock > INT_MAX) { 39 | fprintf(stderr, 40 | "Implementation is wrong. Unsigned result of socket system call " 41 | "can not be stored to signed data type. Please contact the" 42 | "maintainer.\n"); 43 | abort(); 44 | } 45 | return (int)sock; 46 | } 47 | 48 | int mg_socket_create_handle_error(int sock, mg_session *session) { 49 | if (sock == MG_ERROR_SOCKET) { 50 | mg_session_set_error(session, "couldn't open socket: %s", 51 | mg_socket_error()); 52 | return MG_ERROR_NETWORK_FAILURE; 53 | } 54 | return MG_SUCCESS; 55 | } 56 | 57 | int mg_socket_connect(int sock, const struct sockaddr *addr, 58 | socklen_t addrlen) { 59 | int status = connect(sock, addr, addrlen); 60 | if (status != 0) { 61 | return MG_ERROR_SOCKET; 62 | } 63 | return MG_SUCCESS; 64 | } 65 | 66 | int mg_socket_connect_handle_error(int *sock, int status, mg_session *session) { 67 | if (status != MG_SUCCESS) { 68 | mg_session_set_error(session, "couldn't connect to host: %s", 69 | mg_socket_error()); 70 | if (mg_socket_close(*sock) != 0) { 71 | abort(); 72 | } 73 | *sock = MG_ERROR_SOCKET; 74 | return MG_ERROR_NETWORK_FAILURE; 75 | } 76 | return MG_SUCCESS; 77 | } 78 | 79 | int mg_socket_options(int sock, mg_session *session) { 80 | (void)sock; 81 | (void)session; 82 | return MG_SUCCESS; 83 | } 84 | 85 | ssize_t mg_socket_send(int sock, const void *buf, int len) { 86 | int sent = send(sock, buf, len, 0); 87 | if (sent == SOCKET_ERROR) { 88 | return -1; 89 | } 90 | return sent; 91 | } 92 | 93 | ssize_t mg_socket_receive(int sock, void *buf, int len) { 94 | int received = recv(sock, buf, len, 0); 95 | if (received == SOCKET_ERROR) { 96 | return -1; 97 | } 98 | return received; 99 | } 100 | 101 | int mg_socket_poll(struct pollfd *fds, unsigned int nfds, int timeout) { 102 | return WSAPoll(fds, nfds, timeout); 103 | } 104 | 105 | // Implementation here 106 | // https://nlnetlabs.nl/svn/unbound/tags/release-1.0.1/compat/socketpair.c 107 | // does not work. 108 | int mg_socket_pair(int d, int type, int protocol, int *sv) { 109 | (void)d; 110 | (void)type; 111 | (void)protocol; 112 | (void)sv; 113 | return MG_ERROR_UNIMPLEMENTED; 114 | } 115 | 116 | int mg_socket_close(int sock) { 117 | int shutdown_status = shutdown(sock, SD_BOTH); 118 | if (shutdown_status != 0) { 119 | fprintf(stderr, "Fail to shutdown socket: %s\n", mg_socket_error()); 120 | } 121 | int closesocket_status = closesocket(sock); 122 | if (closesocket_status != 0) { 123 | fprintf(stderr, "Fail to close socket: %s\n", mg_socket_error()); 124 | } 125 | return MG_SUCCESS; 126 | } 127 | 128 | char *mg_socket_error() { 129 | // FormatMessage could be used but a caller would have 130 | // to take care of the allocated memory (LocalFree call). 131 | switch (WSAGetLastError()) { 132 | case WSA_INVALID_HANDLE: 133 | return "Specified event object handle is invalid."; 134 | case WSA_NOT_ENOUGH_MEMORY: 135 | return "Insufficient memory available."; 136 | case WSA_INVALID_PARAMETER: 137 | return "One or more parameters are invalid."; 138 | case WSA_OPERATION_ABORTED: 139 | return "Overlapped operation aborted."; 140 | case WSA_IO_INCOMPLETE: 141 | return "Overlapped I/O event object not in signaled state."; 142 | case WSA_IO_PENDING: 143 | return "Overlapped operations will complete later."; 144 | case WSAEINTR: 145 | return "Interrupted function call."; 146 | case WSAEBADF: 147 | return "File handle is not valid."; 148 | case WSAEACCES: 149 | return "Permission denied."; 150 | case WSAEFAULT: 151 | return "Bad address."; 152 | case WSAEINVAL: 153 | return "Invalid argument."; 154 | case WSAEMFILE: 155 | return "Too many open files."; 156 | case WSAEWOULDBLOCK: 157 | return "Resource temporarily unavailable."; 158 | case WSAEINPROGRESS: 159 | return "Operation now in progress."; 160 | case WSAEALREADY: 161 | return "Operation already in progress."; 162 | case WSAENOTSOCK: 163 | return "Socket operation on nonsocket."; 164 | case WSAEDESTADDRREQ: 165 | return "Destination address required."; 166 | case WSAEMSGSIZE: 167 | return "Message too long."; 168 | case WSAEPROTOTYPE: 169 | return "Protocol wrong type for socket."; 170 | case WSAENOPROTOOPT: 171 | return "Bad protocol option."; 172 | case WSAEPROTONOSUPPORT: 173 | return "Protocol not supported."; 174 | case WSAESOCKTNOSUPPORT: 175 | return "Socket type not supported."; 176 | case WSAEOPNOTSUPP: 177 | return "Operation not supported."; 178 | case WSAEPFNOSUPPORT: 179 | return "Protocol family not supported."; 180 | case WSAEAFNOSUPPORT: 181 | return "Address family not supported by protocol family."; 182 | case WSAEADDRINUSE: 183 | return "Address already in use."; 184 | case WSAEADDRNOTAVAIL: 185 | return "Cannot assign requested address."; 186 | case WSAENETDOWN: 187 | return "Network is down."; 188 | case WSAENETUNREACH: 189 | return "Network is unreachable."; 190 | case WSAENETRESET: 191 | return "Network dropped connection on reset."; 192 | case WSAECONNABORTED: 193 | return "Software caused connection abort."; 194 | case WSAECONNRESET: 195 | return "Connection reset by peer."; 196 | case WSAENOBUFS: 197 | return "No buffer space available."; 198 | case WSAEISCONN: 199 | return "Socket is already connected."; 200 | case WSAENOTCONN: 201 | return "Socket is not connected."; 202 | case WSAESHUTDOWN: 203 | return "Cannot send after socket shutdown."; 204 | case WSAETOOMANYREFS: 205 | return "Too many references to some kernel object."; 206 | case WSAETIMEDOUT: 207 | return "Connection timed out."; 208 | case WSAECONNREFUSED: 209 | return "Connection refused."; 210 | case WSAELOOP: 211 | return "Cannot translate name."; 212 | case WSAENAMETOOLONG: 213 | return "Name too long."; 214 | case WSAEHOSTDOWN: 215 | return "Host is down."; 216 | case WSAEHOSTUNREACH: 217 | return "No route to host."; 218 | case WSAENOTEMPTY: 219 | return "Directory not empty."; 220 | case WSAEPROCLIM: 221 | return "Too many processes."; 222 | case WSAEUSERS: 223 | return "User quota exceeded."; 224 | case WSAEDQUOT: 225 | return "Disk quota exceeded."; 226 | case WSAESTALE: 227 | return "Stale file handle reference."; 228 | case WSAEREMOTE: 229 | return "Item is remote."; 230 | case WSASYSNOTREADY: 231 | return "Network subsystem is unavailable."; 232 | case WSAVERNOTSUPPORTED: 233 | return "Winsock.dll version out of range."; 234 | case WSANOTINITIALISED: 235 | return "Successful WSAStartup not yet performed."; 236 | case WSAEDISCON: 237 | return "Graceful shutdown in progress."; 238 | case WSAENOMORE: 239 | return "No more results."; 240 | case WSAECANCELLED: 241 | return "Call has been canceled."; 242 | case WSAEINVALIDPROCTABLE: 243 | return "Procedure call table is invalid."; 244 | case WSAEINVALIDPROVIDER: 245 | return "Service provider is invalid."; 246 | case WSAEPROVIDERFAILEDINIT: 247 | return "Service provider failed to initialize."; 248 | case WSASYSCALLFAILURE: 249 | return "System call failure."; 250 | case WSASERVICE_NOT_FOUND: 251 | return "Service not found."; 252 | case WSATYPE_NOT_FOUND: 253 | return "Class type not found."; 254 | case WSA_E_NO_MORE: 255 | return "No more results."; 256 | case WSA_E_CANCELLED: 257 | return "Call was canceled."; 258 | case WSAEREFUSED: 259 | return "Database query was refused."; 260 | case WSAHOST_NOT_FOUND: 261 | return "Host not found."; 262 | case WSATRY_AGAIN: 263 | return "Nonauthoritative host not found."; 264 | case WSANO_RECOVERY: 265 | return "This is a nonrecoverable error."; 266 | case WSANO_DATA: 267 | return "Valid name, no data record of requested type."; 268 | case WSA_QOS_RECEIVERS: 269 | return "At least one QoS reserve has arrived."; 270 | case WSA_QOS_SENDERS: 271 | return "At least one QoS send path has arrived."; 272 | case WSA_QOS_NO_SENDERS: 273 | return "There are no QoS senders."; 274 | case WSA_QOS_NO_RECEIVERS: 275 | return "There are no QoS receivers."; 276 | case WSA_QOS_REQUEST_CONFIRMED: 277 | return "The QoS reserve request has been confirmed."; 278 | case WSA_QOS_ADMISSION_FAILURE: 279 | return "QoS admission error."; 280 | case WSA_QOS_POLICY_FAILURE: 281 | return "QoS policy failure."; 282 | case WSA_QOS_BAD_STYLE: 283 | return "QoS bad style."; 284 | case WSA_QOS_BAD_OBJECT: 285 | return "QoS bad object."; 286 | case WSA_QOS_TRAFFIC_CTRL_ERROR: 287 | return "QoS traffic control error."; 288 | case WSA_QOS_GENERIC_ERROR: 289 | return "QoS generic error."; 290 | case WSA_QOS_ESERVICETYPE: 291 | return "QoS service type error."; 292 | case WSA_QOS_EFLOWSPEC: 293 | return "QoS flowspec error."; 294 | case WSA_QOS_EPROVSPECBUF: 295 | return "Invalid QoS provider buffer."; 296 | case WSA_QOS_EFILTERSTYLE: 297 | return "Invalid QoS filter style."; 298 | case WSA_QOS_EFILTERTYPE: 299 | return "Invalid QoS filter type."; 300 | case WSA_QOS_EFILTERCOUNT: 301 | return "Incorrect QoS filter count."; 302 | case WSA_QOS_EOBJLENGTH: 303 | return "Invalid QoS object length."; 304 | case WSA_QOS_EFLOWCOUNT: 305 | return "Incorrect QoS flow count."; 306 | case WSA_QOS_EUNKOWNPSOBJ: 307 | return "Unrecognized QoS object."; 308 | case WSA_QOS_EPOLICYOBJ: 309 | return "Invalid QoS policy object."; 310 | case WSA_QOS_EFLOWDESC: 311 | return "Invalid QoS flow descriptor."; 312 | case WSA_QOS_EPSFLOWSPEC: 313 | return "Invalid QoS provider-specific flowspec."; 314 | case WSA_QOS_EPSFILTERSPEC: 315 | return "Invalid QoS provider-specific filterspec."; 316 | case WSA_QOS_ESDMODEOBJ: 317 | return "Invalid QoS shape discard mode object."; 318 | case WSA_QOS_ESHAPERATEOBJ: 319 | return "Invalid QoS shaping rate object."; 320 | case WSA_QOS_RESERVED_PETYPE: 321 | return "Reserved policy QoS element type."; 322 | default: 323 | return "Unknown WSA error."; 324 | } 325 | return "Unknown WSA error."; 326 | } 327 | 328 | void mg_socket_finalize() { 329 | if (WSACleanup() != 0) { 330 | fprintf(stderr, "WSACleanup failed: %s\n", mg_socket_error()); 331 | } 332 | } 333 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016-2020 Memgraph Ltd. [https://memgraph.com] 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | find_package(Threads REQUIRED) 16 | include(FetchContent) 17 | 18 | set(GTEST_GIT_TAG "release-1.8.1" CACHE STRING "GTest git tag") 19 | FetchContent_Declare(googletest 20 | GIT_REPOSITORY https://github.com/google/googletest.git 21 | GIT_TAG ${GTEST_GIT_TAG} 22 | ) 23 | # For Windows: Prevent overriding the parent project's compiler/linker settings 24 | set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) 25 | FetchContent_MakeAvailable(googletest) 26 | 27 | set(TESTS_ROOT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) 28 | macro(add_gtest target_name target_path) 29 | add_executable(${target_name} ${target_path}) 30 | target_include_directories(${target_name} PRIVATE "${PROJECT_SOURCE_DIR}/src" "${TESTS_ROOT_DIRECTORY}") 31 | target_link_libraries(${target_name} mgclient-static mgclient_cpp gtest gtest_main gmock_main project_cpp_warnings) 32 | if (ENABLE_COVERAGE) 33 | add_test(${target_name} env LLVM_PROFILE_FILE=${target_name}.profraw ./${target_name}) 34 | else() 35 | add_test(${target_name} ${target_name}) 36 | endif() 37 | endmacro() 38 | 39 | add_gtest(value value.cpp) 40 | add_gtest(encoder encoder.cpp) 41 | add_gtest(decoder decoder.cpp) 42 | add_gtest(client client.cpp) 43 | # We're mocking the mg_secure_transport_init function in the test. 44 | if(MGCLIENT_ON_APPLE) 45 | target_link_libraries(client -Wl,-alias,___wrap_mg_secure_transport_init,_mg_secure_transport_init) 46 | elseif(MGCLIENT_ON_LINUX) 47 | target_link_libraries(client -Wl,--wrap=mg_secure_transport_init) 48 | endif() 49 | add_gtest(transport transport.cpp) 50 | if(MGCLIENT_ON_APPLE) 51 | target_link_libraries(transport c++) 52 | else() 53 | target_link_libraries(transport stdc++fs) 54 | endif() 55 | add_gtest(allocator allocator.cpp) 56 | add_gtest(unit_mgclient_value unit/mgclient_value.cpp) 57 | 58 | if(BUILD_TESTING_INTEGRATION) 59 | add_gtest(integration_basic_c integration/basic_c.cpp) 60 | add_gtest(integration_basic_cpp integration/basic_cpp.cpp) 61 | endif() 62 | 63 | # Build examples and add them to tests 64 | set(EXAMPLE_DIR ${PROJECT_SOURCE_DIR}/examples) 65 | 66 | add_executable(example_basic_c ${EXAMPLE_DIR}/basic.c) 67 | target_link_libraries(example_basic_c mgclient-static project_c_warnings) 68 | add_test(example_basic_c example_basic_c 127.0.0.1 7687 "RETURN 1") 69 | 70 | add_executable(example_basic_cpp ${EXAMPLE_DIR}/basic.cpp) 71 | target_link_libraries(example_basic_cpp mgclient-static mgclient_cpp project_cpp_warnings) 72 | add_test(example_basic_cpp example_basic_cpp 127.0.0.1 7687 "RETURN 1") 73 | 74 | add_executable(example_advanced_cpp ${EXAMPLE_DIR}/advanced.cpp) 75 | target_link_libraries(example_advanced_cpp mgclient-static mgclient_cpp project_cpp_warnings) 76 | add_test(example_advanced_cpp example_advanced_cpp 127.0.0.1 7687) 77 | -------------------------------------------------------------------------------- /tests/allocator.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2020 Memgraph Ltd. [https://memgraph.com] 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include 16 | 17 | #include "mgallocator.h" 18 | 19 | #include "test-common.hpp" 20 | 21 | using namespace std::string_literals; 22 | 23 | TEST(LinearAllocatorTest, Basic) { 24 | tracking_allocator underlying_allocator; 25 | mg_linear_allocator *allocator = mg_linear_allocator_init( 26 | (mg_allocator *)&underlying_allocator, 4096, 2048); 27 | 28 | ASSERT_EQ(underlying_allocator.allocated.size(), 2u); 29 | 30 | for (int i = 0; i < 4; ++i) { 31 | mg_allocator_malloc((mg_allocator *)allocator, 1024); 32 | } 33 | ASSERT_EQ(underlying_allocator.allocated.size(), 2u); 34 | 35 | mg_allocator_malloc((mg_allocator *)allocator, 1024); 36 | ASSERT_EQ(underlying_allocator.allocated.size(), 3u); 37 | 38 | mg_linear_allocator_reset(allocator); 39 | ASSERT_EQ(underlying_allocator.allocated.size(), 2u); 40 | 41 | for (int i = 0; i < 4; ++i) { 42 | mg_allocator_malloc((mg_allocator *)allocator, 1024); 43 | } 44 | ASSERT_EQ(underlying_allocator.allocated.size(), 2u); 45 | 46 | mg_linear_allocator_destroy(allocator); 47 | ASSERT_EQ(underlying_allocator.allocated.size(), 0u); 48 | } 49 | 50 | TEST(LinearAllocatorTest, SeparateAllocations) { 51 | tracking_allocator underlying_allocator; 52 | mg_linear_allocator *allocator = mg_linear_allocator_init( 53 | (mg_allocator *)&underlying_allocator, 4096, 2048); 54 | 55 | ASSERT_EQ(underlying_allocator.allocated.size(), 2u); 56 | 57 | for (int i = 0; i < 3; ++i) { 58 | mg_allocator_malloc((mg_allocator *)allocator, 1024); 59 | } 60 | ASSERT_EQ(underlying_allocator.allocated.size(), 2u); 61 | 62 | mg_allocator_malloc((mg_allocator *)allocator, 2048); 63 | ASSERT_EQ(underlying_allocator.allocated.size(), 3u); 64 | 65 | mg_allocator_malloc((mg_allocator *)allocator, 1024); 66 | ASSERT_EQ(underlying_allocator.allocated.size(), 3u); 67 | 68 | mg_allocator_malloc((mg_allocator *)allocator, 1024); 69 | ASSERT_EQ(underlying_allocator.allocated.size(), 4u); 70 | 71 | mg_linear_allocator_reset(allocator); 72 | ASSERT_EQ(underlying_allocator.allocated.size(), 2u); 73 | 74 | for (int i = 0; i < 4; ++i) { 75 | mg_allocator_malloc((mg_allocator *)allocator, 1024); 76 | } 77 | ASSERT_EQ(underlying_allocator.allocated.size(), 2u); 78 | 79 | mg_linear_allocator_destroy(allocator); 80 | ASSERT_EQ(underlying_allocator.allocated.size(), 0u); 81 | } 82 | -------------------------------------------------------------------------------- /tests/encoder.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2020 Memgraph Ltd. [https://memgraph.com] 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include 16 | #include 17 | 18 | #include 19 | 20 | #include "mgclient.h" 21 | #include "mgcommon.h" 22 | #include "mgconstants.h" 23 | #include "mgsession.h" 24 | #include "mgsocket.h" 25 | 26 | #include "bolt-testdata.hpp" 27 | #include "test-common.hpp" 28 | 29 | using namespace std::string_literals; 30 | 31 | /// Reads data from socket until it is closed. We read from socket while 32 | /// writing to prevent the kernel buffer from filling up which makes `send` 33 | /// block. 34 | class TestServer { 35 | public: 36 | TestServer() {} 37 | 38 | void Start(int sockfd) { 39 | sockfd_ = sockfd; 40 | thread_ = std::thread([this, sockfd] { 41 | char buffer[8192]; 42 | while (true) { 43 | ssize_t now = mg_socket_receive(sockfd, buffer, 8192); 44 | if (now < 0) { 45 | error = true; 46 | break; 47 | } 48 | if (now == 0) { 49 | break; 50 | } 51 | data.insert(data.end(), buffer, buffer + (size_t)now); 52 | } 53 | }); 54 | } 55 | 56 | void Stop() { 57 | if (thread_.joinable()) { 58 | thread_.join(); 59 | } 60 | close(sockfd_); 61 | } 62 | 63 | bool error{false}; 64 | std::string data; 65 | 66 | private: 67 | int sockfd_; 68 | std::thread thread_; 69 | }; 70 | 71 | class EncoderTest : public ::testing::Test { 72 | protected: 73 | virtual void SetUp() override { 74 | session.out_buffer = 75 | (char *)malloc(MG_BOLT_CHUNK_HEADER_SIZE + MG_BOLT_MAX_CHUNK_SIZE); 76 | session.out_capacity = MG_BOLT_CHUNK_HEADER_SIZE + MG_BOLT_MAX_CHUNK_SIZE; 77 | session.out_begin = MG_BOLT_CHUNK_HEADER_SIZE; 78 | session.out_end = session.out_begin; 79 | { 80 | int tmp[2]; 81 | ASSERT_EQ(mg_socket_pair(AF_UNIX, SOCK_STREAM, 0, tmp), 0); 82 | sc = tmp[0]; 83 | ss = tmp[1]; 84 | } 85 | mg_raw_transport_init(sc, (mg_raw_transport **)&session.transport, 86 | (mg_allocator *)&allocator); 87 | server.Start(ss); 88 | } 89 | 90 | virtual void TearDown() override { free(session.out_buffer); } 91 | 92 | tracking_allocator allocator; 93 | mg_session session; 94 | TestServer server; 95 | 96 | int sc; 97 | int ss; 98 | }; 99 | 100 | void AssertReadRaw(std::stringstream &sstr, const std::string &expected) { 101 | std::vector tmp(expected.size()); 102 | size_t read = sstr.readsome(tmp.data(), expected.size()); 103 | if (read != expected.size()) { 104 | FAIL() << "Expected at least " << expected.size() 105 | << " bytes in stream, got only " << read; 106 | } 107 | std::string got(tmp.data(), expected.size()); 108 | ASSERT_EQ(got, expected); 109 | } 110 | 111 | void AssertEnd(std::stringstream &sstr) { 112 | std::stringstream::char_type got = sstr.get(); 113 | if (got != std::stringstream::traits_type::eof()) { 114 | FAIL() << "Expected end of input stream, got character " 115 | << ::testing::PrintToString(got); 116 | } 117 | } 118 | 119 | void AssertReadMessage(std::stringstream &sstr, const std::string &expected) { 120 | char chunk[65535]; 121 | std::string got; 122 | while (true) { 123 | uint16_t chunk_size; 124 | if (sstr.readsome((char *)&chunk_size, 2) != 2) { 125 | FAIL() << "Not enough chunks in stream"; 126 | } 127 | chunk_size = be16toh(chunk_size); 128 | if (chunk_size == 0) break; 129 | if (sstr.readsome(chunk, chunk_size) != chunk_size) { 130 | FAIL() << "Failed to read entire chunk from stream"; 131 | } 132 | got.insert(got.end(), chunk, chunk + chunk_size); 133 | } 134 | ASSERT_EQ(got, expected); 135 | } 136 | 137 | class MessageChunkingTest : public EncoderTest {}; 138 | 139 | TEST_F(MessageChunkingTest, Empty) { 140 | mg_session_flush_message(&session); 141 | mg_raw_transport_destroy(session.transport); 142 | 143 | server.Stop(); 144 | ASSERT_FALSE(server.error); 145 | std::stringstream sstr(server.data); 146 | 147 | ASSERT_READ_RAW(sstr, "\x00\x00"s); 148 | ASSERT_END(sstr); 149 | ASSERT_MEMORY_OK(); 150 | } 151 | 152 | TEST_F(MessageChunkingTest, Small) { 153 | std::string data = "\x00\x01\x02\x03\x04\x05"s; 154 | mg_session_write_raw(&session, data.data(), data.size()); 155 | mg_session_flush_message(&session); 156 | mg_raw_transport_destroy(session.transport); 157 | 158 | server.Stop(); 159 | ASSERT_FALSE(server.error); 160 | std::stringstream sstr(server.data); 161 | 162 | ASSERT_READ_RAW(sstr, "\x00\x06"s); 163 | ASSERT_READ_RAW(sstr, data); 164 | ASSERT_READ_RAW(sstr, "\x00\x00"s); 165 | ASSERT_END(sstr); 166 | ASSERT_MEMORY_OK(); 167 | } 168 | 169 | TEST_F(MessageChunkingTest, ExactlyOne) { 170 | std::string data(65535, '\0'); 171 | for (int i = 0; i < 65535; ++i) { 172 | data[i] = (char)(i & 0xFF); 173 | } 174 | mg_session_write_raw(&session, data.data(), data.size()); 175 | mg_session_flush_message(&session); 176 | mg_raw_transport_destroy(session.transport); 177 | 178 | server.Stop(); 179 | ASSERT_FALSE(server.error); 180 | std::stringstream sstr(server.data); 181 | 182 | ASSERT_READ_RAW(sstr, "\xFF\xFF"s); 183 | ASSERT_READ_RAW(sstr, data); 184 | ASSERT_READ_RAW(sstr, "\x00\x00"s); 185 | ASSERT_END(sstr); 186 | ASSERT_MEMORY_OK(); 187 | } 188 | 189 | TEST_F(MessageChunkingTest, ManySmall) { 190 | std::string data(1000, '\0'); 191 | for (int i = 0; i < 1000; ++i) { 192 | data[i] = (char)(i & 0xff); 193 | } 194 | 195 | for (int i = 0; i < 100; ++i) { 196 | mg_session_write_raw(&session, data.data(), data.size()); 197 | } 198 | mg_session_flush_message(&session); 199 | mg_raw_transport_destroy(session.transport); 200 | 201 | std::string first_chunk_data(65535, '\0'); 202 | std::string second_chunk_data(100000 - 65535, '\0'); 203 | { 204 | int p; 205 | for (p = 0; p < 65535; ++p) { 206 | first_chunk_data[p] = (char)((p % 1000) & 0xff); 207 | } 208 | for (; p < 100000; ++p) { 209 | second_chunk_data[p - 65535] = (char)((p % 1000) & 0xff); 210 | } 211 | } 212 | 213 | server.Stop(); 214 | ASSERT_FALSE(server.error); 215 | std::stringstream sstr(server.data); 216 | 217 | ASSERT_READ_RAW(sstr, "\xFF\xFF"s); 218 | ASSERT_READ_RAW(sstr, first_chunk_data); 219 | ASSERT_READ_RAW(sstr, "\x86\xA1"s); 220 | ASSERT_READ_RAW(sstr, second_chunk_data); 221 | ASSERT_READ_RAW(sstr, "\x00\x00"s); 222 | ASSERT_END(sstr); 223 | ASSERT_MEMORY_OK(); 224 | } 225 | 226 | TEST_F(MessageChunkingTest, ManyMessages) { 227 | mg_session_write_raw(&session, "abc", 3); 228 | mg_session_flush_message(&session); 229 | // An empty message in between. 230 | mg_session_flush_message(&session); 231 | mg_session_write_raw(&session, "defg", 4); 232 | mg_session_flush_message(&session); 233 | mg_raw_transport_destroy(session.transport); 234 | 235 | server.Stop(); 236 | ASSERT_FALSE(server.error); 237 | std::stringstream sstr(server.data); 238 | 239 | ASSERT_READ_RAW(sstr, "\x00\x03"s); 240 | ASSERT_READ_RAW(sstr, "abc"s); 241 | ASSERT_READ_RAW(sstr, "\x00\x00"s); 242 | ASSERT_READ_RAW(sstr, "\x00\x00"s); 243 | ASSERT_READ_RAW(sstr, "\x00\x04"s); 244 | ASSERT_READ_RAW(sstr, "defg"s); 245 | ASSERT_READ_RAW(sstr, "\x00\x00"s); 246 | ASSERT_END(sstr); 247 | ASSERT_MEMORY_OK(); 248 | } 249 | 250 | class ValueTest : public EncoderTest, 251 | public ::testing::WithParamInterface { 252 | protected: 253 | virtual void TearDown() { EncoderTest::TearDown(); } 254 | }; 255 | 256 | // TODO(mtomic): When these tests fail, just a bunch of bytes is outputted, we 257 | // might want to make this nicer (maybe add names or descriptions to 258 | // parameters). 259 | 260 | TEST_P(ValueTest, Encoding) { 261 | mg_session_write_value(&session, GetParam().decoded); 262 | mg_session_flush_message(&session); 263 | mg_raw_transport_destroy(session.transport); 264 | 265 | server.Stop(); 266 | ASSERT_FALSE(server.error); 267 | std::stringstream sstr(server.data); 268 | 269 | ASSERT_READ_MESSAGE(sstr, GetParam().encoded); 270 | ASSERT_END(sstr); 271 | ASSERT_MEMORY_OK(); 272 | } 273 | 274 | INSTANTIATE_TEST_CASE_P(Null, ValueTest, 275 | ::testing::ValuesIn(NullTestCases()), ); 276 | 277 | INSTANTIATE_TEST_CASE_P(Bool, ValueTest, 278 | ::testing::ValuesIn(BoolTestCases()), ); 279 | 280 | INSTANTIATE_TEST_CASE_P(Integer, ValueTest, 281 | ::testing::ValuesIn(IntegerTestCases()), ); 282 | 283 | INSTANTIATE_TEST_CASE_P(Float, ValueTest, 284 | ::testing::ValuesIn(FloatTestCases()), ); 285 | 286 | INSTANTIATE_TEST_CASE_P(String, ValueTest, 287 | ::testing::ValuesIn(StringTestCases()), ); 288 | 289 | INSTANTIATE_TEST_CASE_P(List, ValueTest, 290 | ::testing::ValuesIn(ListTestCases()), ); 291 | 292 | INSTANTIATE_TEST_CASE_P(Date, ValueTest, 293 | ::testing::ValuesIn(DateTestCases()), ); 294 | 295 | INSTANTIATE_TEST_CASE_P(LocalTime, ValueTest, 296 | ::testing::ValuesIn(LocalTimeTestCases()), ); 297 | 298 | INSTANTIATE_TEST_CASE_P(LocalDateTime, ValueTest, 299 | ::testing::ValuesIn(LocalDateTimeTestCases()), ); 300 | 301 | INSTANTIATE_TEST_CASE_P(Duration, ValueTest, 302 | ::testing::ValuesIn((DurationTestCases())), ); 303 | 304 | INSTANTIATE_TEST_CASE_P(Map, ValueTest, ::testing::ValuesIn(MapTestCases()), ); 305 | -------------------------------------------------------------------------------- /tests/gmock_wrapper.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // On older versions of clang/gcc deprecated-copy might not exist 4 | 5 | #pragma GCC diagnostic push 6 | 7 | #if (defined(__clang_major__) && __clang_major__ >= 10 && \ 8 | !defined(__APPLE__)) || \ 9 | (defined(__GNUC__) && __GNUC__ >= 10) 10 | #pragma GCC diagnostic ignored "-Wdeprecated-copy" 11 | #endif 12 | 13 | #include 14 | 15 | #pragma GCC diagnostic pop 16 | -------------------------------------------------------------------------------- /tests/integration/basic_c.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2020 Memgraph Ltd. [https://memgraph.com] 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include 16 | 17 | #include "mgclient.h" 18 | 19 | #include "gmock_wrapper.h" 20 | 21 | template 22 | T GetEnvOrDefault(const std::string &value_name, const T &default_value) { 23 | const char *char_value = std::getenv(value_name.c_str()); 24 | if (!char_value) return default_value; 25 | T value; 26 | std::stringstream env_value_stream(char_value); 27 | env_value_stream >> value; 28 | return value; 29 | } 30 | 31 | void CheckMgValueType(const mg_value *value, const mg_value_type value_type) { 32 | ASSERT_EQ(mg_value_get_type(value), value_type); 33 | } 34 | 35 | bool GetBoolValue(const mg_value *value) { 36 | CheckMgValueType(value, MG_VALUE_TYPE_BOOL); 37 | return mg_value_bool(value); 38 | } 39 | 40 | int GetIntegerValue(const mg_value *value) { 41 | CheckMgValueType(value, MG_VALUE_TYPE_INTEGER); 42 | return mg_value_integer(value); 43 | } 44 | 45 | double GetFloatValue(const mg_value *value) { 46 | CheckMgValueType(value, MG_VALUE_TYPE_FLOAT); 47 | return mg_value_float(value); 48 | } 49 | 50 | const mg_node *GetNodeValue(const mg_value *value) { 51 | CheckMgValueType(value, MG_VALUE_TYPE_NODE); 52 | return mg_value_node(value); 53 | } 54 | 55 | const mg_relationship *GetRelationshipValue(const mg_value *value) { 56 | CheckMgValueType(value, MG_VALUE_TYPE_RELATIONSHIP); 57 | return mg_value_relationship(value); 58 | } 59 | 60 | std::string GetStringValue(const mg_string *value) { 61 | return std::string(mg_string_data(value), mg_string_size(value)); 62 | } 63 | 64 | std::string GetStringValue(const mg_value *value) { 65 | CheckMgValueType(value, MG_VALUE_TYPE_STRING); 66 | const mg_string *s = mg_value_string(value); 67 | return GetStringValue(s); 68 | } 69 | 70 | const mg_list *GetListValue(const mg_value *value) { 71 | CheckMgValueType(value, MG_VALUE_TYPE_LIST); 72 | return mg_value_list(value); 73 | } 74 | 75 | const mg_map *GetMapValue(const mg_value *value) { 76 | CheckMgValueType(value, MG_VALUE_TYPE_MAP); 77 | return mg_value_map(value); 78 | } 79 | 80 | class MemgraphConnection : public ::testing::Test { 81 | protected: 82 | virtual void SetUp() override { 83 | mg_init(); 84 | params = mg_session_params_make(); 85 | std::string memgraph_host = 86 | GetEnvOrDefault("MEMGRAPH_HOST", "127.0.0.1"); 87 | int memgraph_port = GetEnvOrDefault("MEMGRAPH_PORT", 7687); 88 | bool memgraph_ssl = GetEnvOrDefault("MEMGRAPH_SSLMODE", false); 89 | 90 | mg_session_params_set_host(params, memgraph_host.c_str()); 91 | mg_session_params_set_port(params, memgraph_port); 92 | mg_session_params_set_sslmode( 93 | params, memgraph_ssl ? MG_SSLMODE_REQUIRE : MG_SSLMODE_DISABLE); 94 | ASSERT_EQ(mg_connect(params, &session), 0); 95 | DatabaseCleanup(); 96 | } 97 | 98 | virtual void TearDown() override { 99 | DatabaseCleanup(); 100 | mg_session_params_destroy(params); 101 | if (session) { 102 | mg_session_destroy(session); 103 | } 104 | mg_finalize(); 105 | } 106 | 107 | mg_session_params *params; 108 | mg_session *session; 109 | 110 | void DatabaseCleanup() { 111 | mg_result *result; 112 | const char *delete_all_query = "MATCH (n) DETACH DELETE n"; 113 | 114 | ASSERT_EQ(mg_session_run(session, delete_all_query, nullptr, nullptr, 115 | nullptr, nullptr), 116 | 0); 117 | ASSERT_EQ(mg_session_pull(session, nullptr), 0); 118 | ASSERT_EQ(mg_session_fetch(session, &result), 0); 119 | ASSERT_EQ(mg_session_fetch(session, &result), MG_ERROR_BAD_CALL); 120 | } 121 | }; 122 | 123 | TEST_F(MemgraphConnection, InsertAndRetriveFromMemegraph) { 124 | ASSERT_EQ(mg_session_begin_transaction(session, nullptr), 0); 125 | mg_result *result; 126 | int status = 0, rows = 0; 127 | const char *create_query = 128 | "CREATE (n: TestLabel{id: 1, name: 'test1', is_deleted: true}) " 129 | "CREATE (n)-[:TestRel {attr: 'attr1'}]->(: TestLabel{id: 12, name: " 130 | "'test2', is_deleted: false})"; 131 | const char *get_query = "MATCH (n)-[r]->(m) RETURN n, r, m"; 132 | 133 | ASSERT_EQ( 134 | mg_session_run(session, create_query, nullptr, nullptr, nullptr, nullptr), 135 | 0); 136 | ASSERT_EQ(mg_session_pull(session, nullptr), 0); 137 | ASSERT_EQ(mg_session_fetch(session, &result), 0); 138 | ASSERT_EQ(mg_session_fetch(session, &result), MG_ERROR_BAD_CALL); 139 | 140 | ASSERT_EQ( 141 | mg_session_run(session, get_query, nullptr, nullptr, nullptr, nullptr), 142 | 0); 143 | ASSERT_EQ(mg_session_pull(session, nullptr), 0); 144 | while ((status = mg_session_fetch(session, &result)) == 1) { 145 | const mg_list *mg_columns = mg_result_columns(result); 146 | const mg_list *mg_row = mg_result_row(result); 147 | 148 | ASSERT_EQ(mg_list_size(mg_columns), 3u); 149 | EXPECT_EQ(GetStringValue(mg_list_at(mg_columns, 0)), "n"); 150 | EXPECT_EQ(GetStringValue(mg_list_at(mg_columns, 1)), "r"); 151 | EXPECT_EQ(GetStringValue(mg_list_at(mg_columns, 2)), "m"); 152 | 153 | ASSERT_EQ(mg_list_size(mg_row), 3u); 154 | const mg_node *node_n = GetNodeValue(mg_list_at(mg_row, 0)); 155 | const mg_node *node_m = GetNodeValue(mg_list_at(mg_row, 2)); 156 | const mg_relationship *relationship_r = 157 | GetRelationshipValue(mg_list_at(mg_row, 1)); 158 | 159 | // Assert Labels 160 | ASSERT_EQ(mg_node_label_count(node_n), 1u); 161 | EXPECT_EQ(GetStringValue(mg_node_label_at(node_n, 0)), "TestLabel"); 162 | EXPECT_EQ(GetStringValue(mg_relationship_type(relationship_r)), "TestRel"); 163 | ASSERT_EQ(mg_node_label_count(node_m), 1u); 164 | EXPECT_EQ(GetStringValue(mg_node_label_at(node_m, 0)), "TestLabel"); 165 | 166 | // Assert properties of Node n 167 | const mg_map *properties_n = mg_node_properties(node_n); 168 | ASSERT_EQ(mg_map_size(properties_n), 3u); 169 | EXPECT_EQ(GetIntegerValue(mg_map_at(properties_n, "id")), 1); 170 | EXPECT_EQ(GetStringValue(mg_map_at(properties_n, "name")), "test1"); 171 | EXPECT_EQ(GetBoolValue(mg_map_at(properties_n, "is_deleted")), 1); 172 | 173 | // Assert properties of Node m 174 | const mg_map *properties_m = mg_node_properties(node_m); 175 | ASSERT_EQ(mg_map_size(properties_m), 3u); 176 | EXPECT_EQ(GetIntegerValue(mg_map_at(properties_m, "id")), 12); 177 | EXPECT_EQ(GetStringValue(mg_map_at(properties_m, "name")), "test2"); 178 | EXPECT_EQ(GetBoolValue(mg_map_at(properties_m, "is_deleted")), 0); 179 | 180 | // Assert properties of Relationship r 181 | const mg_map *properties_r = mg_relationship_properties(relationship_r); 182 | ASSERT_EQ(mg_map_size(properties_r), 1u); 183 | EXPECT_EQ(GetStringValue(mg_map_at(properties_r, "attr")), "attr1"); 184 | 185 | rows++; 186 | } 187 | ASSERT_EQ(rows, 1); 188 | ASSERT_EQ(status, 0); 189 | ASSERT_EQ(mg_session_commit_transaction(session, &result), 0); 190 | 191 | // The following test is more suitable to be under unit tests. Since it is 192 | // impossible to execute all unit tests on all platforms (they are not ported 193 | // yet), the test is located under integration tests. 194 | { 195 | ASSERT_EQ(mg_session_run(session, 196 | "CREATE (n:ValuesTest {int: 1, string:'Name', " 197 | "float: 2.3, bool: True, " 198 | "list: [1, 2], map: {key: 'value'}}) RETURN n;", 199 | nullptr, nullptr, nullptr, nullptr), 200 | 0); 201 | ASSERT_EQ(mg_session_pull(session, nullptr), 0); 202 | mg_result *result; 203 | ASSERT_EQ(mg_session_fetch(session, &result), 1); 204 | const mg_list *mg_columns = mg_result_columns(result); 205 | const mg_list *mg_row = mg_result_row(result); 206 | ASSERT_EQ(mg_list_size(mg_columns), 1u); 207 | ASSERT_EQ(mg_list_size(mg_row), 1u); 208 | const mg_node *node = GetNodeValue(mg_list_at(mg_row, 0)); 209 | const mg_map *properties = mg_node_properties(node); 210 | EXPECT_EQ(GetIntegerValue(mg_map_at(properties, "int")), 1); 211 | EXPECT_EQ(GetStringValue(mg_map_at(properties, "string")), "Name"); 212 | EXPECT_LT(std::abs(GetFloatValue(mg_map_at(properties, "float")) - 2.3), 213 | 0.000001); 214 | EXPECT_EQ(GetBoolValue(mg_map_at(properties, "bool")), true); 215 | const mg_list *list_value = GetListValue(mg_map_at(properties, "list")); 216 | ASSERT_EQ(mg_list_size(list_value), 2u); 217 | ASSERT_EQ(GetIntegerValue(mg_list_at(list_value, 0)), 1); 218 | ASSERT_EQ(GetIntegerValue(mg_list_at(list_value, 1)), 2); 219 | const mg_map *map_value = GetMapValue(mg_map_at(properties, "map")); 220 | ASSERT_EQ(mg_map_size(map_value), 1u); 221 | ASSERT_EQ(GetStringValue(mg_map_at(map_value, "key")), "value"); 222 | ASSERT_EQ(mg_session_fetch(session, &result), 0); 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /tests/integration/basic_cpp.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2020 Memgraph Ltd. [https://memgraph.com] 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include 16 | 17 | #include "mgclient.hpp" 18 | 19 | #include "gmock_wrapper.h" 20 | 21 | template 22 | T GetEnvOrDefault(const std::string &value_name, const T &default_value) { 23 | const char *char_value = std::getenv(value_name.c_str()); 24 | if (!char_value) return default_value; 25 | T value; 26 | std::stringstream env_value_stream(char_value); 27 | env_value_stream >> value; 28 | return value; 29 | } 30 | 31 | class MemgraphConnection : public ::testing::Test { 32 | protected: 33 | virtual void SetUp() override { 34 | mg::Client::Init(); 35 | 36 | client = mg::Client::Connect( 37 | {GetEnvOrDefault("MEMGRAPH_HOST", "127.0.0.1"), 38 | GetEnvOrDefault("MEMGRAPH_PORT", 7687), "", "", 39 | GetEnvOrDefault("MEMGRAPH_SSLMODE", false), ""}); 40 | 41 | ASSERT_TRUE(client); 42 | 43 | // Clean the database for the tests 44 | ASSERT_TRUE(client->Execute(delete_all_query)); 45 | ASSERT_FALSE(client->FetchOne()); 46 | } 47 | 48 | virtual void TearDown() override { 49 | // Check if all results are consumed in the tests 50 | ASSERT_FALSE(client->FetchOne()); 51 | 52 | ASSERT_TRUE(client->Execute(delete_all_query)); 53 | ASSERT_FALSE(client->FetchOne()); 54 | 55 | // Deallocate the client because mg_finalize has to be called globally. 56 | client.reset(nullptr); 57 | 58 | mg::Client::Finalize(); 59 | } 60 | 61 | std::unique_ptr client; 62 | const char *delete_all_query = "MATCH (n) DETACH DELETE n"; 63 | }; 64 | 65 | TEST_F(MemgraphConnection, InsertAndRetrieveFromMemgraph) { 66 | ASSERT_NE(client, nullptr); 67 | ASSERT_TRUE(client->BeginTransaction()); 68 | 69 | const auto create_query = 70 | "CREATE (n: TestLabel{id: 1, name: 'test1', is_deleted: true}) " 71 | "CREATE (n)-[:TestRel {attr: 'attr1'}]->(: TestLabel{id: 12, name: " 72 | "'test2', is_deleted: false})"; 73 | ASSERT_TRUE(client->Execute(create_query)); 74 | auto maybe_result = client->FetchOne(); 75 | ASSERT_FALSE(maybe_result); 76 | 77 | const auto get_query = "MATCH (n)-[r]->(m) RETURN n, r, m"; 78 | ASSERT_TRUE(client->Execute(get_query)); 79 | int result_counter = 0; 80 | while ((maybe_result = client->FetchOne())) { 81 | const auto &result = *maybe_result; 82 | ASSERT_EQ(result.size(), 3u); 83 | 84 | ASSERT_EQ(result[0].type(), mg::Value::Type::Node); 85 | const auto node_n = result[0].ValueNode(); 86 | ASSERT_EQ(result[2].type(), mg::Value::Type::Node); 87 | const auto node_m = result[2].ValueNode(); 88 | ASSERT_EQ(result[1].type(), mg::Value::Type::Relationship); 89 | const auto relationship_r = result[1].ValueRelationship(); 90 | 91 | // Assert labels 92 | const auto n_labels = node_n.labels(); 93 | ASSERT_EQ(n_labels.size(), 1u); 94 | EXPECT_EQ(n_labels[0], "TestLabel"); 95 | const auto m_labels = node_m.labels(); 96 | ASSERT_EQ(m_labels.size(), 1u); 97 | EXPECT_EQ(m_labels[0], "TestLabel"); 98 | const auto r_type = relationship_r.type(); 99 | EXPECT_EQ(r_type, "TestRel"); 100 | 101 | // Assert properties of Node n 102 | const auto n_properties = node_n.properties(); 103 | ASSERT_EQ(n_properties.size(), 3u); 104 | EXPECT_EQ(n_properties["id"].ValueInt(), 1); 105 | EXPECT_EQ(n_properties["name"].ValueString(), "test1"); 106 | EXPECT_EQ(n_properties["is_deleted"].ValueBool(), true); 107 | 108 | // Assert properties of Node m 109 | const auto m_properties = node_m.properties(); 110 | ASSERT_EQ(m_properties.size(), 3u); 111 | EXPECT_EQ(m_properties["id"].ValueInt(), 12); 112 | EXPECT_EQ(m_properties["name"].ValueString(), "test2"); 113 | EXPECT_EQ(m_properties["is_deleted"].ValueBool(), false); 114 | 115 | // Assert properties of Relationship r 116 | const auto r_properties = relationship_r.properties(); 117 | ASSERT_EQ(r_properties.size(), 1u); 118 | EXPECT_EQ(r_properties["attr"].ValueString(), "attr1"); 119 | 120 | ++result_counter; 121 | } 122 | ASSERT_EQ(result_counter, 1); 123 | ASSERT_TRUE(client->CommitTransaction()); 124 | 125 | // The following test is more suitable to be under unit tests. Since it is 126 | // impossible to execute all unit tests on all platforms (they are not ported 127 | // yet), the test is located under integration tests. 128 | { 129 | ASSERT_TRUE(client->Execute( 130 | "CREATE (n:ValuesTest {int: 1, string:'Name', float: 2.3, bool: True, " 131 | "list: [1, 2], map: {key: 'value'}}) RETURN n;")); 132 | auto maybe_result = client->FetchOne(); 133 | ASSERT_TRUE(maybe_result); 134 | const auto &row = *maybe_result; 135 | EXPECT_EQ(row.size(), 1u); 136 | const auto node = row[0]; 137 | ASSERT_EQ(node.type(), mg::Value::Type::Node); 138 | const auto node_props = node.ValueNode().properties(); 139 | ASSERT_EQ(node_props["int"].ValueInt(), 1); 140 | ASSERT_EQ(node_props["string"].ValueString(), "Name"); 141 | ASSERT_LT(std::abs(node_props["float"].ValueDouble() - 2.3), 0.000001); 142 | ASSERT_EQ(node_props["bool"].ValueBool(), true); 143 | ASSERT_EQ(node_props["list"].type(), mg::Value::Type::List); 144 | const auto list_value = node_props["list"].ValueList(); 145 | ASSERT_EQ(list_value.size(), 2u); 146 | ASSERT_EQ(list_value[0].ValueInt(), 1); 147 | ASSERT_EQ(list_value[1].ValueInt(), 2); 148 | ASSERT_EQ(node_props["map"].type(), mg::Value::Type::Map); 149 | const auto map_value = node_props["map"].ValueMap(); 150 | ASSERT_EQ(map_value.size(), 1u); 151 | ASSERT_EQ(map_value["key"].ValueString(), "value"); 152 | ASSERT_FALSE(client->FetchOne()); 153 | } 154 | } 155 | 156 | TEST_F(MemgraphConnection, DiscardAllAndFetchAll) { 157 | ASSERT_NE(client, nullptr); 158 | ASSERT_TRUE(client->Execute("UNWIND range(1, 10) AS x CREATE ();")); 159 | client->DiscardAll(); 160 | ASSERT_TRUE(client->Execute("MATCH (n) RETURN n;")); 161 | auto maybe_nodes = client->FetchAll(); 162 | ASSERT_TRUE(maybe_nodes); 163 | const auto &nodes = *maybe_nodes; 164 | ASSERT_EQ(nodes.size(), 10u); 165 | for (const auto &row : nodes) { 166 | const auto &value = row[0]; 167 | ASSERT_EQ(value.type(), mg::Value::Type::Node); 168 | } 169 | } 170 | 171 | TEST_F(MemgraphConnection, ThrowClientException) { 172 | ASSERT_NE(client, nullptr); 173 | ASSERT_TRUE(client->Execute("CREATE(n {name: assert(false)})")); 174 | ASSERT_THROW(client->DiscardAll(), mg::ClientException); 175 | } 176 | -------------------------------------------------------------------------------- /tests/test-common.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2020 Memgraph Ltd. [https://memgraph.com] 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #pragma once 16 | 17 | #include 18 | #include 19 | 20 | #include "mgallocator.h" 21 | 22 | #define ASSERT_READ_RAW(sstr, data) \ 23 | do { \ 24 | SCOPED_TRACE("ASSERT_READ_RAW"); \ 25 | AssertReadRaw((sstr), (data)); \ 26 | ASSERT_NO_FATAL_FAILURE(); \ 27 | } while (0) 28 | 29 | #define ASSERT_READ_MESSAGE(sstr, expected) \ 30 | do { \ 31 | SCOPED_TRACE("ASSERT_READ_MESSAGE"); \ 32 | AssertReadMessage((sstr), (expected)); \ 33 | ASSERT_NO_FATAL_FAILURE(); \ 34 | } while (0) 35 | 36 | #define ASSERT_END(sstr) \ 37 | do { \ 38 | SCOPED_TRACE("ASSERT_END"); \ 39 | AssertEnd((sstr)); \ 40 | } while (0) 41 | 42 | #define ASSERT_MEMORY_OK() \ 43 | do { \ 44 | SCOPED_TRACE("ASSERT_MEMORY_OK"); \ 45 | ASSERT_TRUE(allocator.allocated.empty()); \ 46 | } while (0) 47 | 48 | void *tracking_allocator_malloc(mg_allocator *allocator, size_t size); 49 | void *tracking_allocator_realloc(mg_allocator *allocator, void *buf, 50 | size_t size); 51 | void tracking_allocator_free(mg_allocator *allocator, void *buf); 52 | 53 | struct tracking_allocator { 54 | void *(*malloc)(struct mg_allocator *self, 55 | size_t size) = tracking_allocator_malloc; 56 | void *(*realloc)(struct mg_allocator *self, void *buf, 57 | size_t size) = tracking_allocator_realloc; 58 | void (*free)(struct mg_allocator *self, void *buf) = tracking_allocator_free; 59 | std::set allocated; 60 | }; 61 | 62 | void *tracking_allocator_malloc(mg_allocator *allocator, size_t size) { 63 | std::set *allocated = &((tracking_allocator *)allocator)->allocated; 64 | void *buf = malloc(size); 65 | if (buf != nullptr) { 66 | allocated->insert(buf); 67 | } 68 | return buf; 69 | } 70 | 71 | void *tracking_allocator_realloc(mg_allocator *allocator, void *buf, 72 | size_t size) { 73 | std::set *allocated = &((tracking_allocator *)allocator)->allocated; 74 | assert(size > 0); 75 | if (buf == nullptr) { 76 | return tracking_allocator_malloc(allocator, size); 77 | } else { 78 | auto it = allocated->find(buf); 79 | assert(it != allocated->end()); 80 | allocated->erase(it); 81 | void *new_buf = realloc(buf, size); 82 | if (new_buf != nullptr) { 83 | allocated->insert(new_buf); 84 | } 85 | return new_buf; 86 | } 87 | } 88 | 89 | void tracking_allocator_free(mg_allocator *allocator, void *buf) { 90 | std::set *allocated = &((tracking_allocator *)allocator)->allocated; 91 | if (buf != nullptr) { 92 | auto it = allocated->find(buf); 93 | assert(it != allocated->end()); 94 | allocated->erase(it); 95 | } 96 | free(buf); 97 | } 98 | -------------------------------------------------------------------------------- /tests/transport.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2020 Memgraph Ltd. [https://memgraph.com] 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // TODO(mtomic): Maybe add test for raw transport. 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | #include 24 | #include 25 | 26 | #if MGCLIENT_ON_WINDOWS 27 | // NOTE: 28 | // https://stackoverflow.com/questions/49504648/x509-name-macro-in-c-wont-compile 29 | #define WIN32_LEAN_AND_MEAN 30 | #include 31 | #endif // MGCLIENT_ON_WINDOWS 32 | 33 | extern "C" { 34 | #include "mgclient.h" 35 | #include "mgcommon.h" 36 | #include "mgsocket.h" 37 | #include "mgtransport.h" 38 | } 39 | 40 | #include "gmock_wrapper.h" 41 | #include "test-common.hpp" 42 | 43 | std::pair MakeCertAndKey(const char *name) { 44 | #if OPENSSL_VERSION_NUMBER < 0x30000000L 45 | RSA *rsa = RSA_new(); 46 | BIGNUM *bne = BN_new(); 47 | BN_set_word(bne, RSA_F4); 48 | RSA_generate_key_ex(rsa, 2048, bne, nullptr); 49 | BN_free(bne); 50 | 51 | EVP_PKEY *pkey = EVP_PKEY_new(); 52 | EVP_PKEY_assign_RSA(pkey, rsa); 53 | #else 54 | EVP_PKEY *pkey = EVP_RSA_gen(2048); 55 | #endif 56 | 57 | X509 *x509 = X509_new(); 58 | ASN1_INTEGER_set(X509_get_serialNumber(x509), 1); 59 | X509_gmtime_adj(X509_get_notBefore(x509), 0); 60 | X509_gmtime_adj(X509_get_notAfter(x509), 86400L); 61 | X509_set_pubkey(x509, pkey); 62 | X509_NAME *subject_name; 63 | subject_name = X509_get_subject_name(x509); 64 | X509_NAME_add_entry_by_txt(subject_name, "C", MBSTRING_ASC, 65 | (unsigned char *)"CA", -1, -1, 0); 66 | X509_NAME_add_entry_by_txt(subject_name, "O", MBSTRING_ASC, 67 | (unsigned char *)name, -1, -1, 0); 68 | X509_NAME_add_entry_by_txt(subject_name, "CN", MBSTRING_ASC, 69 | (unsigned char *)"localhost", -1, -1, 0); 70 | return std::make_pair(x509, pkey); 71 | } 72 | 73 | extern int mg_init_ssl; 74 | 75 | class SecureTransportTest : public ::testing::Test { 76 | protected: 77 | static void SetUpTestCase() { 78 | #if OPENSSL_VERSION_NUMBER < 0x10100000L 79 | mg_init_ssl = 0; 80 | SSL_library_init(); 81 | SSL_load_error_strings(); 82 | ERR_load_crypto_strings(); 83 | #endif 84 | } 85 | virtual void SetUp() override { 86 | OPENSSL_init_crypto(OPENSSL_INIT_ADD_ALL_CIPHERS, nullptr); 87 | int sv[2]; 88 | ASSERT_EQ(mg_socket_pair(AF_UNIX, SOCK_STREAM, 0, sv), 0); 89 | sc = sv[0]; 90 | ss = sv[1]; 91 | 92 | // Build server key and certificate. 93 | std::tie(server_cert, server_key) = MakeCertAndKey("server"); 94 | 95 | // Server certificate is self signed. 96 | X509_set_issuer_name(server_cert, X509_get_subject_name(server_cert)); 97 | X509_sign(server_cert, server_key, EVP_sha512()); 98 | 99 | // Build client key and certificate. 100 | X509 *client_cert; 101 | EVP_PKEY *client_key; 102 | std::tie(client_cert, client_key) = MakeCertAndKey("client"); 103 | 104 | // Build CA key and certificate. 105 | EVP_PKEY *ca_key; 106 | std::tie(ca_cert, ca_key) = MakeCertAndKey("ca"); 107 | 108 | // CA certificate is self signed. 109 | X509_set_issuer_name(ca_cert, X509_get_subject_name(ca_cert)); 110 | X509_sign(ca_cert, ca_key, EVP_sha512()); 111 | 112 | // Sign the client certificate with CA key. 113 | X509_set_issuer_name(client_cert, X509_get_subject_name(ca_cert)); 114 | X509_sign(client_cert, ca_key, EVP_sha512()); 115 | 116 | // Write client key and certificates to temporary file. 117 | client_cert_path = std::filesystem::temp_directory_path() / "client.crt"; 118 | BIO *cert_file = BIO_new_file(client_cert_path.string().c_str(), "w"); 119 | PEM_write_bio_X509(cert_file, client_cert); 120 | BIO_free(cert_file); 121 | 122 | client_key_path = std::filesystem::temp_directory_path() / "client.key"; 123 | BIO *key_file = BIO_new_file(client_key_path.string().c_str(), "w"); 124 | PEM_write_bio_PrivateKey(key_file, client_key, nullptr, nullptr, 0, nullptr, 125 | nullptr); 126 | BIO_free(key_file); 127 | 128 | X509_free(client_cert); 129 | EVP_PKEY_free(client_key); 130 | EVP_PKEY_free(ca_key); 131 | } 132 | 133 | virtual void TearDown() override { 134 | X509_free(server_cert); 135 | X509_free(ca_cert); 136 | EVP_PKEY_free(server_key); 137 | } 138 | 139 | void RunServer(const std::function &server_function) { 140 | server_thread = std::thread(server_function); 141 | } 142 | 143 | void StopServer() { 144 | if (server_thread.joinable()) { 145 | server_thread.join(); 146 | } 147 | } 148 | 149 | X509 *server_cert; 150 | X509 *ca_cert; 151 | EVP_PKEY *server_key; 152 | 153 | std::filesystem::path client_cert_path; 154 | std::filesystem::path client_key_path; 155 | 156 | int sc; 157 | int ss; 158 | std::thread server_thread; 159 | 160 | tracking_allocator allocator; 161 | }; 162 | 163 | TEST_F(SecureTransportTest, NoCertificate) { 164 | // Initialize server. 165 | RunServer([this] { 166 | SSL_CTX *ctx; 167 | #if OPENSSL_VERSION_NUMBER < 0x10100000L 168 | ctx = SSL_CTX_new(SSLv23_server_method()); 169 | #else 170 | ctx = SSL_CTX_new(TLS_server_method()); 171 | #endif 172 | SSL_CTX_use_certificate(ctx, server_cert); 173 | SSL_CTX_use_PrivateKey(ctx, server_key); 174 | ASSERT_TRUE(ctx); 175 | SSL *ssl = SSL_new(ctx); 176 | ASSERT_TRUE(ssl); 177 | SSL_set_fd(ssl, ss); 178 | ASSERT_EQ(SSL_accept(ssl), 1); 179 | 180 | char request[5]; 181 | ASSERT_GT(SSL_read(ssl, request, 5), 0); 182 | ASSERT_EQ(strncmp(request, "hello", 5), 0); 183 | ASSERT_GT(SSL_write(ssl, "hello", 5), 0); 184 | 185 | SSL_free(ssl); 186 | SSL_CTX_free(ctx); 187 | }); 188 | 189 | mg_transport *transport; 190 | ASSERT_EQ(mg_secure_transport_init(sc, nullptr, nullptr, 191 | (mg_secure_transport **)&transport, 192 | (mg_allocator *)&allocator), 193 | 0); 194 | ASSERT_EQ(mg_transport_send((mg_transport *)transport, "hello", 5), 0); 195 | 196 | char response[5]; 197 | ASSERT_EQ(mg_transport_recv(transport, response, 5), 0); 198 | ASSERT_EQ(strncmp(response, "hello", 5), 0); 199 | 200 | mg_transport_destroy(transport); 201 | 202 | StopServer(); 203 | } 204 | 205 | TEST_F(SecureTransportTest, WithCertificate) { 206 | // Initialize server. 207 | RunServer([this] { 208 | SSL_CTX *ctx; 209 | #if OPENSSL_VERSION_NUMBER < 0x10100000L 210 | ctx = SSL_CTX_new(SSLv23_server_method()); 211 | #else 212 | ctx = SSL_CTX_new(TLS_server_method()); 213 | #endif 214 | SSL_CTX_use_certificate(ctx, server_cert); 215 | SSL_CTX_use_PrivateKey(ctx, server_key); 216 | { 217 | X509_STORE *store = SSL_CTX_get_cert_store(ctx); 218 | X509_STORE_add_cert(store, ca_cert); 219 | } 220 | SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, 221 | nullptr); 222 | 223 | ASSERT_TRUE(ctx); 224 | SSL *ssl = SSL_new(ctx); 225 | ASSERT_TRUE(ssl); 226 | SSL_set_fd(ssl, ss); 227 | if (SSL_accept(ssl) != 1) { 228 | ERR_print_errors_fp(stderr); 229 | FAIL(); 230 | } 231 | 232 | char request[5]; 233 | ASSERT_GT(SSL_read(ssl, request, 5), 0); 234 | ASSERT_EQ(strncmp(request, "hello", 5), 0); 235 | ASSERT_GT(SSL_write(ssl, "hello", 5), 0); 236 | 237 | SSL_free(ssl); 238 | SSL_CTX_free(ctx); 239 | }); 240 | 241 | mg_transport *transport; 242 | ASSERT_EQ(mg_secure_transport_init(sc, client_cert_path.string().c_str(), 243 | client_key_path.string().c_str(), 244 | (mg_secure_transport **)&transport, 245 | (mg_allocator *)&allocator), 246 | 0); 247 | ASSERT_EQ(mg_transport_send((mg_transport *)transport, "hello", 5), 0); 248 | 249 | char response[5]; 250 | ASSERT_EQ(mg_transport_recv(transport, response, 5), 0); 251 | ASSERT_EQ(strncmp(response, "hello", 5), 0); 252 | 253 | mg_transport_destroy(transport); 254 | 255 | StopServer(); 256 | } 257 | -------------------------------------------------------------------------------- /tests/unit/mgclient_value.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2020 Memgraph Ltd. [https://memgraph.com] 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include 16 | #include 17 | 18 | #include 19 | 20 | #include "mgclient-value.hpp" 21 | #include "mgclient.h" 22 | 23 | using namespace std; 24 | 25 | namespace mg { 26 | 27 | TEST(ValueTest, BasicTypes) { 28 | Value value_null; 29 | Value value_bool1(true); 30 | Value value_bool2(false); 31 | Value value_int1(-13); 32 | Value value_int2(static_cast((1LL << 60))); 33 | Value value_double(3.14159); 34 | Value value_string1("test"); 35 | Value value_string2(std::string("test")); 36 | 37 | // assert correctness: 38 | ASSERT_EQ(value_null.type(), Value::Type::Null); 39 | ASSERT_EQ(value_bool1.type(), Value::Type::Bool); 40 | ASSERT_EQ(value_bool2.type(), Value::Type::Bool); 41 | ASSERT_EQ(value_int1.type(), Value::Type::Int); 42 | ASSERT_EQ(value_int2.type(), Value::Type::Int); 43 | ASSERT_EQ(value_double.type(), Value::Type::Double); 44 | ASSERT_EQ(value_string1.type(), Value::Type::String); 45 | ASSERT_EQ(value_string2.type(), Value::Type::String); 46 | 47 | ASSERT_EQ(value_bool1.ValueBool(), true); 48 | ASSERT_EQ(value_bool2.ValueBool(), false); 49 | ASSERT_EQ(value_int1.ValueInt(), -13); 50 | ASSERT_EQ(value_int2.ValueInt(), 1LL << 60); 51 | ASSERT_EQ(value_double.ValueDouble(), 3.14159); 52 | ASSERT_EQ(value_string1.ValueString(), "test"); 53 | ASSERT_EQ(value_string2.ValueString(), "test"); 54 | 55 | // compare: 56 | ASSERT_EQ(value_string1, value_string2); 57 | ASSERT_NE(value_string1, value_bool1); 58 | ASSERT_NE(value_bool1, value_bool2); 59 | ASSERT_EQ(value_string1.AsConstValue(), value_string2.AsConstValue()); 60 | ASSERT_EQ(value_string1.AsConstValue(), value_string2); 61 | ASSERT_EQ(value_string1, value_string2.AsConstValue()); 62 | ASSERT_NE(value_int1.AsConstValue(), value_int2.AsConstValue()); 63 | ASSERT_NE(value_int1.AsConstValue(), value_int2); 64 | ASSERT_NE(value_int1, value_int2.AsConstValue()); 65 | } 66 | 67 | TEST(ValueTest, CopyValue) { 68 | Value value1(100); 69 | Value value2(value1); 70 | 71 | ASSERT_NE(value1.ptr(), nullptr); 72 | ASSERT_NE(value2.ptr(), nullptr); 73 | ASSERT_NE(value1.ptr(), value2.ptr()); 74 | ASSERT_EQ(value1, value2); 75 | } 76 | 77 | TEST(ValueTest, MoveValue) { 78 | Value value1(100); 79 | auto ptr = value1.ptr(); 80 | Value value2(std::move(value1)); 81 | 82 | ASSERT_NE(ptr, nullptr); 83 | ASSERT_EQ(value1.ptr(), nullptr); 84 | ASSERT_EQ(value2.ptr(), ptr); 85 | } 86 | 87 | TEST(ValueTest, ListConstruction) { 88 | List inner_list(2); 89 | inner_list.Append(Value(1)); 90 | inner_list.Append(Value(false)); 91 | 92 | List list(4); 93 | list.Append(Value("hey")); 94 | list.Append(Value(3.14)); 95 | list.Append(Value(std::move(inner_list))); 96 | 97 | ASSERT_EQ(list.size(), 3u); 98 | ASSERT_EQ(list[0], Value("hey")); 99 | ASSERT_EQ(list[1], Value(3.14)); 100 | ASSERT_EQ(list[2].type(), Value::Type::List); 101 | ASSERT_EQ(list[2].ValueList().size(), 2u); 102 | 103 | ConstList const_list = list.AsConstList(); 104 | ASSERT_EQ(const_list.size(), 3u); 105 | ASSERT_EQ(const_list[0], Value("hey")); 106 | ASSERT_EQ(const_list[1], Value(3.14)); 107 | ASSERT_EQ(const_list[2].type(), Value::Type::List); 108 | ASSERT_EQ(const_list[2].ValueList().size(), 2u); 109 | } 110 | 111 | TEST(ValueTest, ListIterate) { 112 | List list(4); 113 | list.Append(Value("hey")); 114 | list.Append(Value(3.14)); 115 | list.Append(Value(true)); 116 | 117 | std::vector values; 118 | for (const auto value : list) { 119 | values.push_back(value); 120 | } 121 | 122 | ASSERT_EQ(values.size(), 3u); 123 | ASSERT_EQ(values[0], Value("hey")); 124 | ASSERT_EQ(values[1], Value(3.14)); 125 | ASSERT_EQ(values[2], Value(true)); 126 | 127 | values.clear(); 128 | for (const auto value : list.AsConstList()) { 129 | values.push_back(value); 130 | } 131 | 132 | ASSERT_EQ(values.size(), 3u); 133 | ASSERT_EQ(values[0], Value("hey")); 134 | ASSERT_EQ(values[1], Value(3.14)); 135 | ASSERT_EQ(values[2], Value(true)); 136 | } 137 | 138 | TEST(ValueTest, ListAppendCopiedValue) { 139 | Value value(3); 140 | List list(1); 141 | list.Append(value.AsConstValue()); 142 | ASSERT_NE(list[0].ptr(), nullptr); 143 | ASSERT_NE(list[0].ptr(), value.ptr()); 144 | ASSERT_EQ(list[0], value); 145 | } 146 | 147 | TEST(ValueTest, ListAppendMovedValue) { 148 | Value value(3); 149 | auto ptr = value.ptr(); 150 | List list(1); 151 | list.Append(std::move(value)); 152 | ASSERT_EQ(list[0].ptr(), ptr); 153 | ASSERT_EQ(value.ptr(), nullptr); 154 | } 155 | 156 | TEST(ValueTest, ListComparison) { 157 | List list1(4); 158 | List list2(3); 159 | List list3(3); 160 | 161 | list1.Append(Value(1)); 162 | list1.Append(Value(3.14)); 163 | list1.Append(Value(false)); 164 | 165 | // the same for list2: 166 | list2.Append(Value(1)); 167 | list2.Append(Value(3.14)); 168 | list2.Append(Value(false)); 169 | 170 | // list3 is a bit different: 171 | list3.Append(Value(3.14)); 172 | list3.Append(Value(true)); 173 | list3.Append(Value("ciao")); 174 | 175 | ConstList const_list1 = list1.AsConstList(); 176 | ConstList const_list2 = list2.AsConstList(); 177 | ConstList const_list3 = list3.AsConstList(); 178 | 179 | ASSERT_EQ(list1, list2); 180 | ASSERT_EQ(list1, const_list2); 181 | ASSERT_EQ(const_list1, list2); 182 | ASSERT_EQ(const_list1, const_list2); 183 | 184 | ASSERT_NE(list1, list3); 185 | ASSERT_NE(list1, const_list3); 186 | ASSERT_NE(const_list1, list3); 187 | ASSERT_NE(const_list1, const_list3); 188 | } 189 | 190 | TEST(ValueTest, ValueFromList) { 191 | List list(2); 192 | list.Append(Value(1)); 193 | list.Append(Value(2)); 194 | auto ptr = list.ptr(); 195 | Value value(std::move(list)); 196 | 197 | ASSERT_EQ(value.type(), Value::Type::List); 198 | ConstList value_list = value.ValueList(); 199 | ASSERT_EQ(value_list.ptr(), ptr); 200 | ASSERT_EQ(list.ptr(), nullptr); 201 | ASSERT_EQ(value_list[0], Value(1)); 202 | ASSERT_EQ(value_list[1], Value(2)); 203 | } 204 | 205 | TEST(ValueTest, MapConstruction) { 206 | Map map(4); 207 | map.Insert("key 1", Value(1)); 208 | map.Insert("key 2", Value(3.14)); 209 | map.Insert("key 3", Value(false)); 210 | 211 | ASSERT_EQ(map.size(), 3u); 212 | ASSERT_EQ(map["key 1"], Value(1)); 213 | ASSERT_EQ(map["key 2"], Value(3.14)); 214 | ASSERT_EQ(map["key 3"], Value(false)); 215 | 216 | ConstMap const_map = map.AsConstMap(); 217 | ASSERT_EQ(const_map.size(), 3u); 218 | ASSERT_EQ(const_map["key 1"], Value(1)); 219 | ASSERT_EQ(const_map["key 2"], Value(3.14)); 220 | ASSERT_EQ(const_map["key 3"], Value(false)); 221 | } 222 | 223 | TEST(ValueTest, MapIterate) { 224 | Map map(4); 225 | map.Insert("key 1", Value(1)); 226 | map.Insert("key 2", Value("two")); 227 | map.Insert("key 3", Value(3.0)); 228 | 229 | std::vector> values; 230 | for (const auto [key, value] : map) { 231 | values.emplace_back(key, value); 232 | } 233 | 234 | ASSERT_EQ(values.size(), 3u); 235 | ASSERT_EQ(values[0].first, "key 1"); 236 | ASSERT_EQ(values[0].second, Value(1)); 237 | ASSERT_EQ(values[1].first, "key 2"); 238 | ASSERT_EQ(values[1].second, Value("two")); 239 | ASSERT_EQ(values[2].first, "key 3"); 240 | ASSERT_EQ(values[2].second, Value(3.0)); 241 | 242 | values.clear(); 243 | for (const auto [key, value] : map.AsConstMap()) { 244 | values.emplace_back(key, value); 245 | } 246 | 247 | ASSERT_EQ(values.size(), 3u); 248 | ASSERT_EQ(values[0].first, "key 1"); 249 | ASSERT_EQ(values[0].second, Value(1)); 250 | ASSERT_EQ(values[1].first, "key 2"); 251 | ASSERT_EQ(values[1].second, Value("two")); 252 | ASSERT_EQ(values[2].first, "key 3"); 253 | ASSERT_EQ(values[2].second, Value(3.0)); 254 | } 255 | 256 | TEST(ValueTest, MapInsertCopiedValue) { 257 | Value value(100); 258 | Map map(1); 259 | map.Insert("key", value.AsConstValue()); 260 | 261 | ASSERT_NE(value.ptr(), nullptr); 262 | ASSERT_NE(map["key"].ptr(), value.ptr()); 263 | ASSERT_EQ(map["key"], value); 264 | } 265 | 266 | TEST(ValueTest, MapInsertMovedValue) { 267 | Value value(100); 268 | auto ptr = value.ptr(); 269 | Map map(1); 270 | map.Insert("key", std::move(value)); 271 | ASSERT_EQ(value.ptr(), nullptr); 272 | ASSERT_EQ(map["key"].ptr(), ptr); 273 | } 274 | 275 | TEST(ValueTest, MapComparison) { 276 | Map map1(4); 277 | Map map2(3); 278 | Map map3(3); 279 | 280 | map1.Insert("key 1", Value("ciao")); 281 | map1.Insert("key 2", Value(13)); 282 | map1.Insert("key 3", Value(false)); 283 | 284 | // map2 is the same, but insertion order is different: 285 | map2.Insert("key 2", Value(13)); 286 | map2.Insert("key 3", Value(false)); 287 | map2.Insert("key 1", Value("ciao")); 288 | 289 | // map3 is a bit different: 290 | map3.Insert("key 1", Value("ciao")); 291 | map3.Insert("key 2", Value(false)); 292 | map3.Insert("key 3", Value(13)); 293 | 294 | ConstMap const_map1 = map1.AsConstMap(); 295 | ConstMap const_map2 = map2.AsConstMap(); 296 | ConstMap const_map3 = map3.AsConstMap(); 297 | 298 | ASSERT_EQ(map1, map2); 299 | ASSERT_EQ(map1, const_map2); 300 | ASSERT_EQ(const_map1, map2); 301 | ASSERT_EQ(const_map1, const_map2); 302 | 303 | ASSERT_NE(map1, map3); 304 | ASSERT_NE(map1, const_map3); 305 | ASSERT_NE(const_map1, map3); 306 | ASSERT_NE(const_map1, const_map3); 307 | } 308 | 309 | TEST(ValueTest, MapFind) { 310 | Map map(1); 311 | map.Insert("key 1", Value(1)); 312 | 313 | auto it = map.find("key 1"); 314 | 315 | ASSERT_FALSE(it == map.end()); 316 | ASSERT_EQ((*it).first, "key 1"); 317 | ASSERT_EQ((*it).second, Value(1)); 318 | 319 | ASSERT_TRUE(map.find("key 2") == map.end()); 320 | } 321 | 322 | TEST(ValueTest, ValueFromMap) { 323 | Map map(1); 324 | map.Insert("key 1", Value(13)); 325 | auto ptr = map.ptr(); 326 | Value value(std::move(map)); 327 | 328 | ASSERT_EQ(value.type(), Value::Type::Map); 329 | ConstMap value_map = value.ValueMap(); 330 | ASSERT_EQ(value_map.ptr(), ptr); 331 | ASSERT_EQ(map.ptr(), nullptr); 332 | auto it = value_map.find("key 1"); 333 | ASSERT_TRUE(it != value_map.end()); 334 | ASSERT_EQ((*it).second, Value(13)); 335 | } 336 | 337 | } // namespace mg 338 | -------------------------------------------------------------------------------- /tool/coverage.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | script_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 4 | project_dir="$( dirname "$script_dir" )" 5 | source_dir="$project_dir/src" 6 | include_dir="$project_dir/include" 7 | tests_dir="$project_dir/build/tests" 8 | 9 | generated_dir="$script_dir/generated" 10 | html_dir="$generated_dir/html" 11 | data_file="$generated_dir/default.profdata" 12 | json_file="$generated_dir/report.json" 13 | coverage_file="$generated_dir/coverage.json" 14 | summary_file="$generated_dir/summary.rmu" 15 | 16 | # cleanup output directory 17 | if [ -d "$generated_dir" ]; then 18 | rm -rf "$generated_dir" 19 | fi 20 | mkdir "$generated_dir" 21 | 22 | # merge raw coverage info 23 | raw_files="$( find "$tests_dir" -name "*.profraw" | sort | tr '\n' ' ' )" 24 | llvm-profdata merge -sparse $raw_files -o "$data_file" 25 | 26 | # create list of binaries 27 | obj_files="$( echo "$raw_files" | \ 28 | sed "s/\.profraw//g" | \ 29 | sed -r "s/ +$//g" | \ 30 | sed "s/ / -object /g" )" 31 | 32 | # create list of source files 33 | src_files=$( find "$source_dir" "$include_dir" \ 34 | \( -name '*.c' -o -name '*.h' \) \ 35 | -print | sort | tr '\n' ' ' ) 36 | 37 | # generate html output 38 | llvm-cov show $obj_files \ 39 | -format html \ 40 | -instr-profile "$data_file" \ 41 | -o "$html_dir" \ 42 | -show-line-counts-or-regions \ 43 | -Xdemangler c++filt -Xdemangler -n \ 44 | $src_files 45 | 46 | # generate json output 47 | llvm-cov export $obj_files \ 48 | -instr-profile "$data_file" \ 49 | -Xdemangler c++filt -Xdemangler -n \ 50 | $src_files > "$json_file" 51 | -------------------------------------------------------------------------------- /tool/format.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -Eeuo pipefail 4 | 5 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" 6 | cd "$DIR/.." 7 | 8 | ALL_FILES_TO_CHECK=$(find . -type f -regex ".*\.\(cpp\|hpp\|c\|h\)$" -print | paste -sd " ") 9 | for file in ${ALL_FILES_TO_CHECK}; do 10 | clang-format --style=file -i -verbose "${file}" 11 | done 12 | changes="$(git diff)" 13 | if [[ ! -z "${changes}" ]]; then 14 | echo "Clang-format check failed!" 15 | echo "You should fix the following formatting errors:" 16 | echo "${changes}" 17 | exit 1 18 | else 19 | echo "Clang-format check ok!" 20 | exit 0 21 | fi 22 | -------------------------------------------------------------------------------- /wasm/install_deps.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 4 | git clone https://github.com/emscripten-core/emsdk.git ${DIR}/emsdk 5 | ${DIR}/emsdk/emsdk install 3.1.9 6 | ${DIR}/emsdk/emsdk activate 3.1.9 7 | --------------------------------------------------------------------------------