pax_global_header00006660000000000000000000000064146033345140014515gustar00rootroot0000000000000052 comment=57291e2244ebb6c32784cd81244ba88d0e53b8b9 hiredict/000077500000000000000000000000001460333451400126545ustar00rootroot00000000000000hiredict/.builds/000077500000000000000000000000001460333451400142145ustar00rootroot00000000000000hiredict/.builds/alpine.yml000066400000000000000000000011501460333451400162040ustar00rootroot00000000000000# SPDX-FileCopyrightText: 2024 Hiredict Contributors # SPDX-License-Identifier: LGPL-3.0-or-later image: alpine/latest packages: - openssl-dev - libevent-dev - linux-headers - moreutils - reuse sources: - https://codeberg.org/redict/hiredict.git - https://codeberg.org/redict/redict.git tasks: - lint: | cd hiredict ./lint.sh - build: | cd hiredict make USE_SSL=1 TEST_ASYNC=1 - redict: | cd redict make -C src V=1 USE_JEMALLOC=no MALLOC=libc BUILD_TLS=yes - test: | cd hiredict REDICT_SERVER=../redict/src/redict-server TEST_SSL=1 ./test.sh hiredict/.gitignore000066400000000000000000000001651460333451400146460ustar00rootroot00000000000000/hiredict-test /examples/hiredict-example* /*.o /*.so /*.dylib /*.a **/*.pc build/ *.dSYM tags compile_commands.json hiredict/.reuse/000077500000000000000000000000001460333451400140555ustar00rootroot00000000000000hiredict/.reuse/dep5000066400000000000000000000015011460333451400146320ustar00rootroot00000000000000Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: hiredict Upstream-Contact: Anna (navi) Figueiredo Gomes Source: https://redict.io Files: .gitignore .travis.yml appveyor.yml Copyright: Salvatore Sanfilippo License: BSD-3-Clause Files: hiredict.pc.in hiredict.targets hiredict_ssl.pc.in Copyright: Salvatore Sanfilippo License: BSD-3-Clause Files: shim/hiredis.pc.in shim/hiredis_ssl.pc.in Copyright: Salvatore Sanfilippo License: BSD-3-Clause Files: hiredict-config.cmake.in hiredict_ssl-config.cmake.in Copyright: Salvatore Sanfilippo License: BSD-3-Clause Files: hiredict.targets Copyright: Salvatore Sanfilippo License: BSD-3-Clause hiredict/.travis.yml000066400000000000000000000062531460333451400147730ustar00rootroot00000000000000language: c compiler: - gcc - clang os: - linux - osx dist: bionic branches: only: - staging - trying - master - /^release\/.*$/ install: - if [ "$TRAVIS_COMPILER" != "mingw" ]; then wget https://github.com/redis/redis/archive/6.0.6.tar.gz; tar -xzvf 6.0.6.tar.gz; pushd redis-6.0.6 && BUILD_TLS=yes make && export PATH=$PWD/src:$PATH && popd; fi; before_script: - if [ "$TRAVIS_OS_NAME" == "osx" ]; then curl -O https://distfiles.macports.org/MacPorts/MacPorts-2.6.2-10.13-HighSierra.pkg; sudo installer -pkg MacPorts-2.6.2-10.13-HighSierra.pkg -target /; export PATH=$PATH:/opt/local/bin && sudo port -v selfupdate; sudo port -N install openssl redis; fi; addons: apt: packages: - libc6-dbg - libc6-dev - libc6:i386 - libc6-dev-i386 - libc6-dbg:i386 - gcc-multilib - g++-multilib - libssl-dev - libssl-dev:i386 - valgrind env: - BITS="32" - BITS="64" script: - EXTRA_CMAKE_OPTS="-DENABLE_EXAMPLES:BOOL=ON -DENABLE_SSL:BOOL=ON -DENABLE_SSL_TESTS:BOOL=ON"; if [ "$TRAVIS_OS_NAME" == "osx" ]; then if [ "$BITS" == "32" ]; then CFLAGS="-m32 -Werror"; CXXFLAGS="-m32 -Werror"; LDFLAGS="-m32"; EXTRA_CMAKE_OPTS=; else CFLAGS="-Werror"; CXXFLAGS="-Werror"; fi; else TEST_PREFIX="valgrind --track-origins=yes --leak-check=full"; if [ "$BITS" == "32" ]; then CFLAGS="-m32 -Werror"; CXXFLAGS="-m32 -Werror"; LDFLAGS="-m32"; EXTRA_CMAKE_OPTS=; else CFLAGS="-Werror"; CXXFLAGS="-Werror"; fi; fi; export CFLAGS CXXFLAGS LDFLAGS TEST_PREFIX EXTRA_CMAKE_OPTS - make && make clean; if [ "$TRAVIS_OS_NAME" == "osx" ]; then if [ "$BITS" == "64" ]; then OPENSSL_PREFIX="$(ls -d /usr/local/Cellar/openssl@1.1/*)" USE_SSL=1 make; fi; else USE_SSL=1 make; fi; - mkdir build/ && cd build/ - cmake .. ${EXTRA_CMAKE_OPTS} - make VERBOSE=1 - if [ "$BITS" == "64" ]; then TEST_SSL=1 SKIPS_AS_FAILS=1 ctest -V; else SKIPS_AS_FAILS=1 ctest -V; fi; jobs: include: # Windows MinGW cross compile on Linux - os: linux dist: xenial compiler: mingw addons: apt: packages: - ninja-build - gcc-mingw-w64-x86-64 - g++-mingw-w64-x86-64 script: - mkdir build && cd build - CC=x86_64-w64-mingw32-gcc CXX=x86_64-w64-mingw32-g++ cmake .. -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_BUILD_WITH_INSTALL_RPATH=on - ninja -v # Windows MSVC 2017 - os: windows compiler: msvc env: - MATRIX_EVAL="CC=cl.exe && CXX=cl.exe" before_install: - eval "${MATRIX_EVAL}" install: - choco install ninja - choco install -y memurai-developer script: - mkdir build && cd build - cmd.exe //C 'C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools\VC\Auxiliary\Build\vcvarsall.bat' amd64 '&&' cmake .. -G Ninja -DCMAKE_BUILD_TYPE=Release -DENABLE_EXAMPLES=ON '&&' ninja -v - ./hiredict-test.exe hiredict/CMakeLists.txt000066400000000000000000000222021460333451400154120ustar00rootroot00000000000000# SPDX-FileCopyrightText: 2024 Hiredict Contributors # SPDX-FileCopyrightText: 2024 Salvatore Sanfilippo # # SPDX-License-Identifier: BSD-3-Clause # SPDX-License-Identifier: LGPL-3.0-or-later CMAKE_MINIMUM_REQUIRED(VERSION 3.0.0) MACRO(getVersionBit name) SET(VERSION_REGEX "^#define ${name} (.+)$") FILE(STRINGS "${CMAKE_CURRENT_SOURCE_DIR}/hiredict.h" VERSION_BIT REGEX ${VERSION_REGEX}) STRING(REGEX REPLACE ${VERSION_REGEX} "\\1" ${name} "${VERSION_BIT}") ENDMACRO(getVersionBit) getVersionBit(HIREDICT_MAJOR) getVersionBit(HIREDICT_MINOR) getVersionBit(HIREDICT_PATCH) getVersionBit(HIREDICT_SONAME) SET(VERSION "${HIREDICT_MAJOR}.${HIREDICT_MINOR}.${HIREDICT_PATCH}") MESSAGE("Detected version: ${VERSION}") PROJECT(hiredict LANGUAGES "C" VERSION "${VERSION}") INCLUDE(GNUInstallDirs) OPTION(BUILD_SHARED_LIBS "Build shared libraries" ON) OPTION(ENABLE_SSL "Build hiredict_ssl for SSL support" OFF) OPTION(DISABLE_TESTS "If tests should be compiled or not" OFF) OPTION(ENABLE_SSL_TESTS "Should we test SSL connections" OFF) OPTION(ENABLE_EXAMPLES "Enable building hiredict examples" OFF) OPTION(ENABLE_ASYNC_TESTS "Should we run all asynchronous API tests" OFF) # Historically, the NuGet file was always install; default # to ON for those who rely on that historical behaviour. OPTION(ENABLE_NUGET "Install NuGET packaging details" ON) # Hiredict requires C99 SET(CMAKE_C_STANDARD 99) SET(CMAKE_DEBUG_POSTFIX d) SET(hiredict_sources alloc.c async.c hiredict.c net.c read.c sds.c sockcompat.c) SET(hiredict_sources ${hiredict_sources}) IF(WIN32) ADD_DEFINITIONS(-D_CRT_SECURE_NO_WARNINGS -DWIN32_LEAN_AND_MEAN) ENDIF() ADD_LIBRARY(hiredict ${hiredict_sources}) ADD_LIBRARY(hiredict::hiredict ALIAS hiredict) set(hiredict_export_name hiredict CACHE STRING "Name of the exported target") set(PREFIX "${CMAKE_INSTALL_PREFIX}") set(LIBDIR "${CMAKE_INSTALL_LIBDIR}") set_target_properties(hiredict PROPERTIES EXPORT_NAME ${hiredict_export_name}) SET_TARGET_PROPERTIES(hiredict PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS TRUE VERSION "${HIREDICT_SONAME}") IF(MSVC) SET_TARGET_PROPERTIES(hiredict PROPERTIES COMPILE_FLAGS /Z7) ENDIF() IF(WIN32) TARGET_LINK_LIBRARIES(hiredict PUBLIC ws2_32 crypt32) ELSEIF(CMAKE_SYSTEM_NAME MATCHES "FreeBSD") TARGET_LINK_LIBRARIES(hiredict PUBLIC m) ELSEIF(CMAKE_SYSTEM_NAME MATCHES "SunOS") TARGET_LINK_LIBRARIES(hiredict PUBLIC socket) ENDIF() TARGET_INCLUDE_DIRECTORIES(hiredict PUBLIC $ $) CONFIGURE_FILE(hiredict.pc.in hiredict.pc @ONLY) CONFIGURE_FILE(shim/hiredis.pc.in hiredis.pc @ONLY) set(CPACK_PACKAGE_VENDOR "Redict") set(CPACK_PACKAGE_DESCRIPTION "\ Hiredict is a minimalistic C client library for the Redict database. It is minimalistic because it just adds minimal support for the protocol, \ but at the same time it uses a high level printf-alike API in order to make \ it much higher level than otherwise suggested by its minimal code base and the \ lack of explicit bindings for every Redict command. Apart from supporting sending commands and receiving replies, it comes with a \ reply parser that is decoupled from the I/O layer. It is a stream parser designed \ for easy reusability, which can for instance be used in higher level language bindings \ for efficient reply parsing. Hiredict only supports the binary-safe Redis protocol. The library comes with multiple APIs. There is the synchronous API, the asynchronous API \ and the reply parsing API.") set(CPACK_PACKAGE_HOMEPAGE_URL "https://codeberg.org/redict/hiredict") set(CPACK_PACKAGE_CONTACT "navi@vlhl.dev") set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON) set(CPACK_RPM_PACKAGE_AUTOREQPROV ON) include(CPack) INSTALL(TARGETS hiredict EXPORT hiredict-targets RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) if (MSVC AND BUILD_SHARED_LIBS) INSTALL(FILES $ DESTINATION ${CMAKE_INSTALL_BINDIR} CONFIGURATIONS Debug RelWithDebInfo) endif() if (ENABLE_NUGET) # For NuGet packages INSTALL(FILES hiredict.targets DESTINATION build/native) endif() INSTALL(FILES hiredict.h read.h sds.h async.h alloc.h sockcompat.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/hiredict) INSTALL(FILES shim/hiredis.h shim/read.h shim/sds.h shim/async.h shim/alloc.h shim/sockcompat.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/hiredis) INSTALL(DIRECTORY adapters DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/hiredict) INSTALL(DIRECTORY shim/adapters DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/hiredis) INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/hiredict.pc DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/hiredis.pc DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) export(EXPORT hiredict-targets FILE "${CMAKE_CURRENT_BINARY_DIR}/hiredict-targets.cmake" NAMESPACE hiredict::) if(WIN32) SET(CMAKE_CONF_INSTALL_DIR share/hiredict) else() SET(CMAKE_CONF_INSTALL_DIR ${CMAKE_INSTALL_LIBDIR}/cmake/hiredict) endif() SET(INCLUDE_INSTALL_DIR include) include(CMakePackageConfigHelpers) write_basic_package_version_file("${CMAKE_CURRENT_BINARY_DIR}/hiredict-config-version.cmake" COMPATIBILITY SameMajorVersion) configure_package_config_file(hiredict-config.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/hiredict-config.cmake INSTALL_DESTINATION ${CMAKE_CONF_INSTALL_DIR} PATH_VARS INCLUDE_INSTALL_DIR) INSTALL(EXPORT hiredict-targets FILE hiredict-targets.cmake NAMESPACE hiredict:: DESTINATION ${CMAKE_CONF_INSTALL_DIR}) INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/hiredict-config.cmake ${CMAKE_CURRENT_BINARY_DIR}/hiredict-config-version.cmake DESTINATION ${CMAKE_CONF_INSTALL_DIR}) IF(ENABLE_SSL) IF (NOT OPENSSL_ROOT_DIR) IF (APPLE) SET(OPENSSL_ROOT_DIR "/usr/local/opt/openssl") ENDIF() ENDIF() FIND_PACKAGE(OpenSSL REQUIRED) SET(hiredict_ssl_sources ssl.c) ADD_LIBRARY(hiredict_ssl ${hiredict_ssl_sources}) ADD_LIBRARY(hiredict::hiredict_ssl ALIAS hiredict_ssl) IF (APPLE AND BUILD_SHARED_LIBS) SET_PROPERTY(TARGET hiredict_ssl PROPERTY LINK_FLAGS "-Wl,-undefined -Wl,dynamic_lookup") ENDIF() SET_TARGET_PROPERTIES(hiredict_ssl PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS TRUE VERSION "${HIREDICT_SONAME}") IF(MSVC) SET_TARGET_PROPERTIES(hiredict_ssl PROPERTIES COMPILE_FLAGS /Z7) ENDIF() TARGET_LINK_LIBRARIES(hiredict_ssl PRIVATE OpenSSL::SSL) IF(WIN32) TARGET_LINK_LIBRARIES(hiredict_ssl PRIVATE hiredict) ENDIF() CONFIGURE_FILE(hiredict_ssl.pc.in hiredict_ssl.pc @ONLY) CONFIGURE_FILE(shim/hiredis_ssl.pc.in hiredis_ssl.pc @ONLY) INSTALL(TARGETS hiredict_ssl EXPORT hiredict_ssl-targets RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) if (MSVC AND BUILD_SHARED_LIBS) INSTALL(FILES $ DESTINATION ${CMAKE_INSTALL_BINDIR} CONFIGURATIONS Debug RelWithDebInfo) endif() INSTALL(FILES hiredict_ssl.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/hiredict) INSTALL(FILES shim/hiredis_ssl.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/hiredis) INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/hiredict_ssl.pc DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/hiredis_ssl.pc DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) export(EXPORT hiredict_ssl-targets FILE "${CMAKE_CURRENT_BINARY_DIR}/hiredict_ssl-targets.cmake" NAMESPACE hiredict::) if(WIN32) SET(CMAKE_CONF_INSTALL_DIR share/hiredict_ssl) else() SET(CMAKE_CONF_INSTALL_DIR ${CMAKE_INSTALL_LIBDIR}/cmake/hiredict_ssl) endif() configure_package_config_file(hiredict_ssl-config.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/hiredict_ssl-config.cmake INSTALL_DESTINATION ${CMAKE_CONF_INSTALL_DIR} PATH_VARS INCLUDE_INSTALL_DIR) INSTALL(EXPORT hiredict_ssl-targets FILE hiredict_ssl-targets.cmake NAMESPACE hiredict:: DESTINATION ${CMAKE_CONF_INSTALL_DIR}) INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/hiredict_ssl-config.cmake DESTINATION ${CMAKE_CONF_INSTALL_DIR}) ENDIF() IF(NOT DISABLE_TESTS) ENABLE_TESTING() ADD_EXECUTABLE(hiredict-test test.c) TARGET_LINK_LIBRARIES(hiredict-test hiredict) IF(ENABLE_SSL_TESTS) ADD_DEFINITIONS(-DHIREDICT_TEST_SSL=1) TARGET_LINK_LIBRARIES(hiredict-test hiredict_ssl) ENDIF() IF(ENABLE_ASYNC_TESTS) ADD_DEFINITIONS(-DHIREDICT_TEST_ASYNC=1) TARGET_LINK_LIBRARIES(hiredict-test event) ENDIF() ADD_TEST(NAME hiredict-test COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/test.sh) ENDIF() # Add examples IF(ENABLE_EXAMPLES) ADD_SUBDIRECTORY(examples) ENDIF(ENABLE_EXAMPLES) hiredict/LICENSES/000077500000000000000000000000001460333451400140615ustar00rootroot00000000000000hiredict/LICENSES/BSD-3-Clause.txt000066400000000000000000000026641460333451400166140ustar00rootroot00000000000000Copyright (c) . Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. hiredict/LICENSES/LGPL-3.0-or-later.txt000066400000000000000000001221621460333451400174050ustar00rootroot00000000000000GNU LESSER GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public License, supplemented by the additional permissions listed below. 0. Additional Definitions. As used herein, "this License" refers to version 3 of the GNU Lesser General Public License, and the "GNU GPL" refers to version 3 of the GNU General Public License. "The Library" refers to a covered work governed by this License, other than an Application or a Combined Work as defined below. An "Application" is any work that makes use of an interface provided by the Library, but which is not otherwise based on the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by the Library. A "Combined Work" is a work produced by combining or linking an Application with the Library. The particular version of the Library with which the Combined Work was made is also called the "Linked Version". The "Minimal Corresponding Source" for a Combined Work means the Corresponding Source for the Combined Work, excluding any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not on the Linked Version. The "Corresponding Application Code" for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work. 1. Exception to Section 3 of the GNU GPL. You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL. 2. Conveying Modified Versions. If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may convey a copy of the modified version: a) under this License, provided that you make a good faith effort to ensure that, in the event an Application does not supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, or b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy. 3. Object Code Incorporating Material from Library Header Files. The object code form of an Application may incorporate material from a header file that is part of the Library. You may convey such object code under terms of your choice, provided that, if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or fewer lines in length), you do both of the following: a) Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the object code with a copy of the GNU GPL and this license document. 4. Combined Works. You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following: a) Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the Combined Work with a copy of the GNU GPL and this license document. c) For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document. d) Do one of the following: 0) Convey the Minimal Corresponding Source under the terms of this License, and the Corresponding Application Code in a form suitable for, and under terms that permit, the user to recombine or relink the Application with a modified version of the Linked Version to produce a modified Combined Work, in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source. 1) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (a) uses at run time a copy of the Library already present on the user's computer system, and (b) will operate properly with a modified version of the Library that is interface-compatible with the Linked Version. e) Provide Installation Information, but only if you would otherwise be required to provide such information under section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.) 5. Combined Libraries. You may place library facilities that are a work based on the Library side by side in a single library together with other library facilities that are not Applications and are not covered by this License, and convey such a combined library under terms of your choice, if you do both of the following: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities, conveyed under the terms of this License. b) Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 6. Revised Versions of the GNU Lesser General Public License. The Free Software Foundation may publish revised and/or new versions of the GNU Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation. If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library. GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright © 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. “This License” refers to version 3 of the GNU General Public License. “Copyright” also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. “The Program” refers to any copyrightable work licensed under this License. Each licensee is addressed as “you”. “Licensees” and “recipients” may be individuals or organizations. To “modify” a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a “modified version” of the earlier work or a work “based on” the earlier work. A “covered work” means either the unmodified Program or a work based on the Program. To “propagate” a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To “convey” a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays “Appropriate Legal Notices” to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The “source code” for a work means the preferred form of the work for making modifications to it. “Object code” means any non-source form of a work. A “Standard Interface” means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The “System Libraries” of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A “Major Component”, in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The “Corresponding Source” for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to “keep intact all notices”. c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an “aggregate” if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A “User Product” is either (1) a “consumer product”, which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, “normally used” refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. “Installation Information” for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. “Additional permissions” are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered “further restrictions” within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An “entity transaction” is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A “contributor” is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's “contributor version”. A contributor's “essential patent claims” are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, “control” includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a “patent license” is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To “grant” such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. “Knowingly relying” means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is “discriminatory” if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License “or any later version” applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM “AS IS” WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the “copyright” line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an “about box”. You should also get your employer (if you work as a programmer) or school, if any, to sign a “copyright disclaimer” for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . hiredict/Makefile000066400000000000000000000325571460333451400143300ustar00rootroot00000000000000# Hiredis Makefile # Copyright (C) 2010-2011 Salvatore Sanfilippo # Copyright (C) 2010-2011 Pieter Noordhuis # # SPDX-FileCopyrightText: 2024 Hiredict Contributors # SPDX-FileCopyrightText: 2024 Salvatore Sanfilippo # SPDX-FileCopyrightText: 2024 Pieter Noordhuis # # SPDX-License-Identifier: BSD-3-Clause # SPDX-License-Identifier: LGPL-3.0-or-later OBJ=alloc.o net.o hiredict.o sds.o async.o read.o sockcompat.o EXAMPLES=hiredict-example hiredict-example-libevent hiredict-example-libev hiredict-example-glib hiredict-example-push hiredict-example-poll TESTS=hiredict-test LIBNAME=libhiredict PKGCONFNAME=hiredict.pc SHIM_PKGCONFNAME=shim/hiredis.pc HIREDICT_MAJOR=$(shell grep HIREDICT_MAJOR hiredict.h | awk '{print $$3}') HIREDICT_MINOR=$(shell grep HIREDICT_MINOR hiredict.h | awk '{print $$3}') HIREDICT_PATCH=$(shell grep HIREDICT_PATCH hiredict.h | awk '{print $$3}') HIREDICT_SONAME=$(shell grep HIREDICT_SONAME hiredict.h | awk '{print $$3}') HIREDICT_VERSION=$(HIREDICT_MAJOR).$(HIREDICT_MINOR).$(HIREDICT_PATCH) # Installation related variables and target PREFIX?=/usr/local INCLUDE_PATH?=include/hiredict SHIM_INCLUDE_PATH?=include/hiredis LIBRARY_PATH?=lib PKGCONF_PATH?=pkgconfig INSTALL_INCLUDE_PATH= $(DESTDIR)$(PREFIX)/$(INCLUDE_PATH) SHIM_INSTALL_INCLUDE_PATH= $(DESTDIR)$(PREFIX)/$(SHIM_INCLUDE_PATH) INSTALL_LIBRARY_PATH= $(DESTDIR)$(PREFIX)/$(LIBRARY_PATH) INSTALL_PKGCONF_PATH= $(INSTALL_LIBRARY_PATH)/$(PKGCONF_PATH) # redict-server configuration used for testing REDICT_PORT=56379 REDICT_SERVER=redict-server define REDICT_TEST_CONFIG daemonize yes pidfile /tmp/hiredict-test-redict.pid port $(REDICT_PORT) bind 127.0.0.1 unixsocket /tmp/hiredict-test-redict.sock endef export REDICT_TEST_CONFIG # Fallback to gcc when $CC is not in $PATH. CC:=$(shell sh -c 'type $${CC%% *} >/dev/null 2>/dev/null && echo $(CC) || echo gcc') CXX:=$(shell sh -c 'type $${CXX%% *} >/dev/null 2>/dev/null && echo $(CXX) || echo g++') OPTIMIZATION?=-O3 WARNINGS=-Wall -Wextra -Wstrict-prototypes -Wwrite-strings -Wno-missing-field-initializers USE_WERROR?=1 ifeq ($(USE_WERROR),1) WARNINGS+=-Werror endif DEBUG_FLAGS?= -g -ggdb REAL_CFLAGS=$(OPTIMIZATION) -fPIC $(CPPFLAGS) $(CFLAGS) $(WARNINGS) $(DEBUG_FLAGS) $(PLATFORM_FLAGS) REAL_LDFLAGS=$(LDFLAGS) DYLIBSUFFIX=so STLIBSUFFIX=a DYLIB_MINOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDICT_SONAME) DYLIB_MAJOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDICT_MAJOR) DYLIBNAME=$(LIBNAME).$(DYLIBSUFFIX) DYLIB_MAKE_CMD=$(CC) $(PLATFORM_FLAGS) -shared -Wl,-soname,$(DYLIB_MINOR_NAME) STLIBNAME=$(LIBNAME).$(STLIBSUFFIX) STLIB_MAKE_CMD=$(AR) rcs #################### SSL variables start #################### SSL_OBJ=ssl.o SSL_LIBNAME=libhiredict_ssl SSL_PKGCONFNAME=hiredict_ssl.pc SSL_SHIM_PKGCONFNAME=shim/hiredis_ssl.pc SSL_INSTALLNAME=install-ssl SSL_DYLIB_MINOR_NAME=$(SSL_LIBNAME).$(DYLIBSUFFIX).$(HIREDICT_SONAME) SSL_DYLIB_MAJOR_NAME=$(SSL_LIBNAME).$(DYLIBSUFFIX).$(HIREDICT_MAJOR) SSL_DYLIBNAME=$(SSL_LIBNAME).$(DYLIBSUFFIX) SSL_STLIBNAME=$(SSL_LIBNAME).$(STLIBSUFFIX) SSL_DYLIB_MAKE_CMD=$(CC) $(PLATFORM_FLAGS) -shared -Wl,-soname,$(SSL_DYLIB_MINOR_NAME) USE_SSL?=0 ifeq ($(USE_SSL),1) # This is required for test.c only CFLAGS+=-DHIREDICT_TEST_SSL EXAMPLES+=hiredict-example-ssl hiredict-example-libevent-ssl SSL_STLIB=$(SSL_STLIBNAME) SSL_DYLIB=$(SSL_DYLIBNAME) SSL_PKGCONF=$(SSL_PKGCONFNAME) SSL_SHIM_PKGCONF=$(SSL_SHIM_PKGCONFNAME) SSL_INSTALL=$(SSL_INSTALLNAME) else SSL_STLIB= SSL_DYLIB= SSL_PKGCONF= SSL_SHIM_PKGCONF= SSL_INSTALL= endif ##################### SSL variables end ##################### # Platform-specific overrides uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not') # This is required for test.c only ifeq ($(TEST_ASYNC),1) export CFLAGS+=-DHIREDICT_TEST_ASYNC endif ifeq ($(USE_SSL),1) ifndef OPENSSL_PREFIX ifeq ($(uname_S),Darwin) SEARCH_PATH1=/opt/homebrew/opt/openssl SEARCH_PATH2=/usr/local/opt/openssl ifneq ($(wildcard $(SEARCH_PATH1)),) OPENSSL_PREFIX=$(SEARCH_PATH1) else ifneq ($(wildcard $(SEARCH_PATH2)),) OPENSSL_PREFIX=$(SEARCH_PATH2) endif endif endif ifdef OPENSSL_PREFIX CFLAGS+=-I$(OPENSSL_PREFIX)/include SSL_LDFLAGS+=-L$(OPENSSL_PREFIX)/lib endif SSL_LDFLAGS+=-lssl -lcrypto endif ifeq ($(uname_S),FreeBSD) LDFLAGS+=-lm IS_GCC=$(shell sh -c '$(CC) --version 2>/dev/null |egrep -i -c "gcc"') ifeq ($(IS_GCC),1) REAL_CFLAGS+=-pedantic endif else REAL_CFLAGS+=-pedantic endif ifeq ($(uname_S),SunOS) IS_SUN_CC=$(shell sh -c '$(CC) -V 2>&1 |egrep -i -c "sun|studio"') ifeq ($(IS_SUN_CC),1) SUN_SHARED_FLAG=-G else SUN_SHARED_FLAG=-shared endif REAL_LDFLAGS+= -ldl -lnsl -lsocket DYLIB_MAKE_CMD=$(CC) $(SUN_SHARED_FLAG) -o $(DYLIBNAME) -h $(DYLIB_MINOR_NAME) $(LDFLAGS) SSL_DYLIB_MAKE_CMD=$(CC) $(SUN_SHARED_FLAG) -o $(SSL_DYLIBNAME) -h $(SSL_DYLIB_MINOR_NAME) $(LDFLAGS) $(SSL_LDFLAGS) endif ifeq ($(uname_S),Darwin) DYLIBSUFFIX=dylib DYLIB_MINOR_NAME=$(LIBNAME).$(HIREDICT_SONAME).$(DYLIBSUFFIX) DYLIB_MAKE_CMD=$(CC) -dynamiclib -Wl,-install_name,$(PREFIX)/$(LIBRARY_PATH)/$(DYLIB_MINOR_NAME) -o $(DYLIBNAME) $(LDFLAGS) SSL_DYLIB_MAKE_CMD=$(CC) -dynamiclib -Wl,-install_name,$(PREFIX)/$(LIBRARY_PATH)/$(SSL_DYLIB_MINOR_NAME) -o $(SSL_DYLIBNAME) $(LDFLAGS) $(SSL_LDFLAGS) DYLIB_PLUGIN=-Wl,-undefined -Wl,dynamic_lookup endif all: dynamic static hiredict-test pkgconfig dynamic: $(DYLIBNAME) $(SSL_DYLIB) static: $(STLIBNAME) $(SSL_STLIB) pkgconfig: $(PKGCONFNAME) $(SHIM_PKGCONFNAME) $(SSL_PKGCONF) $(SSL_SHIM_PKGCONF) # Deps (use make dep to generate this) alloc.o: alloc.c fmacros.h alloc.h async.o: async.c fmacros.h alloc.h async.h hiredict.h read.h sds.h net.h dict.c dict.h win32.h async_private.h dict.o: dict.c fmacros.h alloc.h dict.h hiredict.o: hiredict.c fmacros.h hiredict.h read.h sds.h alloc.h net.h async.h win32.h net.o: net.c fmacros.h net.h hiredict.h read.h sds.h alloc.h sockcompat.h win32.h read.o: read.c fmacros.h alloc.h read.h sds.h win32.h sds.o: sds.c sds.h sdsalloc.h alloc.h sockcompat.o: sockcompat.c sockcompat.h test.o: test.c fmacros.h hiredict.h read.h sds.h alloc.h net.h sockcompat.h win32.h $(DYLIBNAME): $(OBJ) $(DYLIB_MAKE_CMD) -o $(DYLIBNAME) $(OBJ) $(REAL_LDFLAGS) $(STLIBNAME): $(OBJ) $(STLIB_MAKE_CMD) $(STLIBNAME) $(OBJ) #################### SSL building rules start #################### $(SSL_DYLIBNAME): $(SSL_OBJ) $(SSL_DYLIB_MAKE_CMD) $(DYLIB_PLUGIN) -o $(SSL_DYLIBNAME) $(SSL_OBJ) $(REAL_LDFLAGS) $(LDFLAGS) $(SSL_LDFLAGS) $(SSL_STLIBNAME): $(SSL_OBJ) $(STLIB_MAKE_CMD) $(SSL_STLIBNAME) $(SSL_OBJ) $(SSL_OBJ): ssl.c hiredict.h read.h sds.h alloc.h async.h win32.h async_private.h #################### SSL building rules end #################### # Binaries: hiredict-example-libevent: examples/example-libevent.c adapters/libevent.h $(STLIBNAME) $(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< -levent $(STLIBNAME) $(REAL_LDFLAGS) hiredict-example-libevent-ssl: examples/example-libevent-ssl.c adapters/libevent.h $(STLIBNAME) $(SSL_STLIBNAME) $(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< -levent $(STLIBNAME) $(SSL_STLIBNAME) $(REAL_LDFLAGS) $(SSL_LDFLAGS) hiredict-example-libev: examples/example-libev.c adapters/libev.h $(STLIBNAME) $(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< -lev $(STLIBNAME) $(REAL_LDFLAGS) hiredict-example-libhv: examples/example-libhv.c adapters/libhv.h $(STLIBNAME) $(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< -lhv $(STLIBNAME) $(REAL_LDFLAGS) hiredict-example-glib: examples/example-glib.c adapters/glib.h $(STLIBNAME) $(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< $(shell pkg-config --cflags --libs glib-2.0) $(STLIBNAME) $(REAL_LDFLAGS) hiredict-example-ivykis: examples/example-ivykis.c adapters/ivykis.h $(STLIBNAME) $(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< -livykis $(STLIBNAME) $(REAL_LDFLAGS) hiredict-example-macosx: examples/example-macosx.c adapters/macosx.h $(STLIBNAME) $(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< -framework CoreFoundation $(STLIBNAME) $(REAL_LDFLAGS) hiredict-example-ssl: examples/example-ssl.c $(STLIBNAME) $(SSL_STLIBNAME) $(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< $(STLIBNAME) $(SSL_STLIBNAME) $(REAL_LDFLAGS) $(SSL_LDFLAGS) hiredict-example-poll: examples/example-poll.c adapters/poll.h $(STLIBNAME) $(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< $(STLIBNAME) $(REAL_LDFLAGS) ifndef AE_DIR hiredict-example-ae: @echo "Please specify AE_DIR (e.g. /src)" @false else hiredict-example-ae: examples/example-ae.c adapters/ae.h $(STLIBNAME) $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. -I$(AE_DIR) $< $(AE_DIR)/ae.o $(AE_DIR)/zmalloc.o $(AE_DIR)/../deps/jemalloc/lib/libjemalloc.a -pthread $(STLIBNAME) endif ifndef LIBUV_DIR # dynamic link libuv.so hiredict-example-libuv: examples/example-libuv.c adapters/libuv.h $(STLIBNAME) $(CC) -o examples/$@ $(REAL_CFLAGS) -I. -I$(LIBUV_DIR)/include $< -luv -lpthread -lrt $(STLIBNAME) $(REAL_LDFLAGS) else # use user provided static lib hiredict-example-libuv: examples/example-libuv.c adapters/libuv.h $(STLIBNAME) $(CC) -o examples/$@ $(REAL_CFLAGS) -I. -I$(LIBUV_DIR)/include $< $(LIBUV_DIR)/.libs/libuv.a -lpthread -lrt $(STLIBNAME) $(REAL_LDFLAGS) endif ifeq ($(and $(QT_MOC),$(QT_INCLUDE_DIR),$(QT_LIBRARY_DIR)),) hiredict-example-qt: @echo "Please specify QT_MOC, QT_INCLUDE_DIR AND QT_LIBRARY_DIR" @false else hiredict-example-qt: examples/example-qt.cpp adapters/qt.h $(STLIBNAME) $(QT_MOC) adapters/qt.h -I. -I$(QT_INCLUDE_DIR) -I$(QT_INCLUDE_DIR)/QtCore | \ $(CXX) -x c++ -o qt-adapter-moc.o -c - $(REAL_CFLAGS) -I. -I$(QT_INCLUDE_DIR) -I$(QT_INCLUDE_DIR)/QtCore $(QT_MOC) examples/example-qt.h -I. -I$(QT_INCLUDE_DIR) -I$(QT_INCLUDE_DIR)/QtCore | \ $(CXX) -x c++ -o qt-example-moc.o -c - $(REAL_CFLAGS) -I. -I$(QT_INCLUDE_DIR) -I$(QT_INCLUDE_DIR)/QtCore $(CXX) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. -I$(QT_INCLUDE_DIR) -I$(QT_INCLUDE_DIR)/QtCore -L$(QT_LIBRARY_DIR) qt-adapter-moc.o qt-example-moc.o $< -pthread $(STLIBNAME) -lQtCore endif hiredict-example: examples/example.c $(STLIBNAME) $(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< $(STLIBNAME) $(REAL_LDFLAGS) hiredict-example-push: examples/example-push.c $(STLIBNAME) $(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< $(STLIBNAME) $(REAL_LDFLAGS) examples: $(EXAMPLES) TEST_LIBS = $(STLIBNAME) $(SSL_STLIB) TEST_LDFLAGS = $(SSL_LDFLAGS) ifeq ($(USE_SSL),1) TEST_LDFLAGS += -pthread endif ifeq ($(TEST_ASYNC),1) TEST_LDFLAGS += -levent endif hiredict-test: test.o $(TEST_LIBS) $(CC) -o $@ $(REAL_CFLAGS) -I. $^ $(REAL_LDFLAGS) $(TEST_LDFLAGS) hiredict-%: %.o $(STLIBNAME) $(CC) $(REAL_CFLAGS) -o $@ $< $(TEST_LIBS) $(REAL_LDFLAGS) test: hiredict-test ./hiredict-test check: hiredict-test TEST_SSL=$(USE_SSL) ./test.sh .c.o: $(CC) -std=c99 -c $(REAL_CFLAGS) $< clean: rm -rf $(DYLIBNAME) $(STLIBNAME) $(SSL_DYLIBNAME) $(SSL_STLIBNAME) $(TESTS) \ $(PKGCONFNAME) $(SHIM_PKGCONFNAME) $(SSL_PKGCONFNAME) $(SSL_SHIM_PKGCONFNAME) \ examples/hiredict-example* *.o *.gcda *.gcno *.gcov dep: $(CC) $(CPPFLAGS) $(CFLAGS) -MM *.c INSTALL?= cp -pPR %.pc: %.pc.in @echo "Generating $@ for pkgconfig..." @sed -e 's#@PREFIX@#$(PREFIX)#' \ -e 's#@LIBDIR@#$(LIBRARY_PATH)#' \ -e 's#@PROJECT_VERSION@#$(HIREDICT_VERSION)#' \ "$<" > "$@" install: $(DYLIBNAME) $(STLIBNAME) $(PKGCONFNAME) $(SHIM_PKGCONFNAME) $(SSL_INSTALL) mkdir -p $(INSTALL_INCLUDE_PATH) $(INSTALL_INCLUDE_PATH)/adapters $(INSTALL_LIBRARY_PATH) $(SHIM_INSTALL_INCLUDE_PATH) $(SHIM_INSTALL_INCLUDE_PATH)/adapters $(INSTALL) hiredict.h async.h read.h sds.h alloc.h sockcompat.h $(INSTALL_INCLUDE_PATH) $(INSTALL) shim/hiredis.h shim/async.h shim/read.h shim/sds.h shim/alloc.h shim/sockcompat.h $(SHIM_INSTALL_INCLUDE_PATH) $(INSTALL) adapters/*.h $(INSTALL_INCLUDE_PATH)/adapters $(INSTALL) shim/adapters/*.h $(SHIM_INSTALL_INCLUDE_PATH)/adapters $(INSTALL) $(DYLIBNAME) $(INSTALL_LIBRARY_PATH)/$(DYLIB_MINOR_NAME) cd $(INSTALL_LIBRARY_PATH) && ln -sf $(DYLIB_MINOR_NAME) $(DYLIBNAME) && ln -sf $(DYLIB_MINOR_NAME) $(DYLIB_MAJOR_NAME) $(INSTALL) $(STLIBNAME) $(INSTALL_LIBRARY_PATH) mkdir -p $(INSTALL_PKGCONF_PATH) $(INSTALL) $(PKGCONFNAME) $(SHIM_PKGCONFNAME) $(INSTALL_PKGCONF_PATH) install-ssl: $(SSL_DYLIBNAME) $(SSL_STLIBNAME) $(SSL_PKGCONFNAME) $(SSL_SHIM_PKGCONFNAME) mkdir -p $(INSTALL_INCLUDE_PATH) $(INSTALL_LIBRARY_PATH) $(SHIM_INSTALL_INCLUDE_PATH) $(INSTALL) hiredict_ssl.h $(INSTALL_INCLUDE_PATH) $(INSTALL) shim/hiredis_ssl.h $(SHIM_INSTALL_INCLUDE_PATH) $(INSTALL) $(SSL_DYLIBNAME) $(INSTALL_LIBRARY_PATH)/$(SSL_DYLIB_MINOR_NAME) cd $(INSTALL_LIBRARY_PATH) && ln -sf $(SSL_DYLIB_MINOR_NAME) $(SSL_DYLIBNAME) && ln -sf $(SSL_DYLIB_MINOR_NAME) $(SSL_DYLIB_MAJOR_NAME) $(INSTALL) $(SSL_STLIBNAME) $(INSTALL_LIBRARY_PATH) mkdir -p $(INSTALL_PKGCONF_PATH) $(INSTALL) $(SSL_PKGCONFNAME) $(SSL_SHIM_PKGCONFNAME) $(INSTALL_PKGCONF_PATH) 32bit: @echo "" @echo "WARNING: if this fails under Linux you probably need to install libc6-dev-i386" @echo "" $(MAKE) CFLAGS="-m32" LDFLAGS="-m32" 32bit-vars: $(eval CFLAGS=-m32) $(eval LDFLAGS=-m32) gprof: $(MAKE) CFLAGS="-pg" LDFLAGS="-pg" gcov: $(MAKE) CFLAGS+="-fprofile-arcs -ftest-coverage" LDFLAGS="-fprofile-arcs" coverage: gcov make check mkdir -p tmp/lcov lcov -d . -c --exclude '/usr*' -o tmp/lcov/hiredict.info lcov -q -l tmp/lcov/hiredict.info genhtml --legend -q -o tmp/lcov/report tmp/lcov/hiredict.info noopt: $(MAKE) OPTIMIZATION="" .PHONY: all test check clean dep install 32bit 32bit-vars gprof gcov noopt hiredict/adapters/000077500000000000000000000000001460333451400144575ustar00rootroot00000000000000hiredict/adapters/ae.h000066400000000000000000000057111460333451400152210ustar00rootroot00000000000000/* * Copyright (c) 2010-2011, Pieter Noordhuis * * SPDX-FileCopyrightText: 2024 Hiredict Contributors * SPDX-FileCopyrightText: 2024 Pieter Noordhuis * * SPDX-License-Identifier: BSD-3-Clause * SPDX-License-Identifier: LGPL-3.0-or-later * */ #ifndef __HIREDICT_AE_H__ #define __HIREDICT_AE_H__ #include #include #include "../hiredict.h" #include "../async.h" typedef struct redictAeEvents { redictAsyncContext *context; aeEventLoop *loop; int fd; int reading, writing; } redictAeEvents; static void redictAeReadEvent(aeEventLoop *el, int fd, void *privdata, int mask) { ((void)el); ((void)fd); ((void)mask); redictAeEvents *e = (redictAeEvents*)privdata; redictAsyncHandleRead(e->context); } static void redictAeWriteEvent(aeEventLoop *el, int fd, void *privdata, int mask) { ((void)el); ((void)fd); ((void)mask); redictAeEvents *e = (redictAeEvents*)privdata; redictAsyncHandleWrite(e->context); } static void redictAeAddRead(void *privdata) { redictAeEvents *e = (redictAeEvents*)privdata; aeEventLoop *loop = e->loop; if (!e->reading) { e->reading = 1; aeCreateFileEvent(loop,e->fd,AE_READABLE,redictAeReadEvent,e); } } static void redictAeDelRead(void *privdata) { redictAeEvents *e = (redictAeEvents*)privdata; aeEventLoop *loop = e->loop; if (e->reading) { e->reading = 0; aeDeleteFileEvent(loop,e->fd,AE_READABLE); } } static void redictAeAddWrite(void *privdata) { redictAeEvents *e = (redictAeEvents*)privdata; aeEventLoop *loop = e->loop; if (!e->writing) { e->writing = 1; aeCreateFileEvent(loop,e->fd,AE_WRITABLE,redictAeWriteEvent,e); } } static void redictAeDelWrite(void *privdata) { redictAeEvents *e = (redictAeEvents*)privdata; aeEventLoop *loop = e->loop; if (e->writing) { e->writing = 0; aeDeleteFileEvent(loop,e->fd,AE_WRITABLE); } } static void redictAeCleanup(void *privdata) { redictAeEvents *e = (redictAeEvents*)privdata; redictAeDelRead(privdata); redictAeDelWrite(privdata); hi_free(e); } static int redictAeAttach(aeEventLoop *loop, redictAsyncContext *ac) { redictContext *c = &(ac->c); redictAeEvents *e; /* Nothing should be attached when something is already attached */ if (ac->ev.data != NULL) return REDICT_ERR; /* Create container for context and r/w events */ e = (redictAeEvents*)hi_malloc(sizeof(*e)); if (e == NULL) return REDICT_ERR; e->context = ac; e->loop = loop; e->fd = c->fd; e->reading = e->writing = 0; /* Register functions to start/stop listening for events */ ac->ev.addRead = redictAeAddRead; ac->ev.delRead = redictAeDelRead; ac->ev.addWrite = redictAeAddWrite; ac->ev.delWrite = redictAeDelWrite; ac->ev.cleanup = redictAeCleanup; ac->ev.data = e; return REDICT_OK; } #endif hiredict/adapters/glib.h000066400000000000000000000102141460333451400155430ustar00rootroot00000000000000/* * Copyright (c) 2009-2011, Salvatore Sanfilippo * * SPDX-FileCopyrightText: 2024 Hiredict Contributors * SPDX-FileCopyrightText: 2024 Salvatore Sanfilippo * * SPDX-License-Identifier: BSD-3-Clause * SPDX-License-Identifier: LGPL-3.0-or-later * */ #ifndef __HIREDICT_GLIB_H__ #define __HIREDICT_GLIB_H__ #include #include "../hiredict.h" #include "../async.h" typedef struct { GSource source; redictAsyncContext *ac; GPollFD poll_fd; } RedictSource; static void redict_source_add_read (gpointer data) { RedictSource *source = (RedictSource *)data; g_return_if_fail(source); source->poll_fd.events |= G_IO_IN; g_main_context_wakeup(g_source_get_context((GSource *)data)); } static void redict_source_del_read (gpointer data) { RedictSource *source = (RedictSource *)data; g_return_if_fail(source); source->poll_fd.events &= ~G_IO_IN; g_main_context_wakeup(g_source_get_context((GSource *)data)); } static void redict_source_add_write (gpointer data) { RedictSource *source = (RedictSource *)data; g_return_if_fail(source); source->poll_fd.events |= G_IO_OUT; g_main_context_wakeup(g_source_get_context((GSource *)data)); } static void redict_source_del_write (gpointer data) { RedictSource *source = (RedictSource *)data; g_return_if_fail(source); source->poll_fd.events &= ~G_IO_OUT; g_main_context_wakeup(g_source_get_context((GSource *)data)); } static void redict_source_cleanup (gpointer data) { RedictSource *source = (RedictSource *)data; g_return_if_fail(source); redict_source_del_read(source); redict_source_del_write(source); /* * It is not our responsibility to remove ourself from the * current main loop. However, we will remove the GPollFD. */ if (source->poll_fd.fd >= 0) { g_source_remove_poll((GSource *)data, &source->poll_fd); source->poll_fd.fd = -1; } } static gboolean redict_source_prepare (GSource *source, gint *timeout_) { RedictSource *redict = (RedictSource *)source; *timeout_ = -1; return !!(redict->poll_fd.events & redict->poll_fd.revents); } static gboolean redict_source_check (GSource *source) { RedictSource *redict = (RedictSource *)source; return !!(redict->poll_fd.events & redict->poll_fd.revents); } static gboolean redict_source_dispatch (GSource *source, GSourceFunc callback, gpointer user_data) { RedictSource *redict = (RedictSource *)source; if ((redict->poll_fd.revents & G_IO_OUT)) { redictAsyncHandleWrite(redict->ac); redict->poll_fd.revents &= ~G_IO_OUT; } if ((redict->poll_fd.revents & G_IO_IN)) { redictAsyncHandleRead(redict->ac); redict->poll_fd.revents &= ~G_IO_IN; } if (callback) { return callback(user_data); } return TRUE; } static void redict_source_finalize (GSource *source) { RedictSource *redict = (RedictSource *)source; if (redict->poll_fd.fd >= 0) { g_source_remove_poll(source, &redict->poll_fd); redict->poll_fd.fd = -1; } } static GSource * redict_source_new (redictAsyncContext *ac) { static GSourceFuncs source_funcs = { .prepare = redict_source_prepare, .check = redict_source_check, .dispatch = redict_source_dispatch, .finalize = redict_source_finalize, }; redictContext *c = &ac->c; RedictSource *source; g_return_val_if_fail(ac != NULL, NULL); source = (RedictSource *)g_source_new(&source_funcs, sizeof *source); if (source == NULL) return NULL; source->ac = ac; source->poll_fd.fd = c->fd; source->poll_fd.events = 0; source->poll_fd.revents = 0; g_source_add_poll((GSource *)source, &source->poll_fd); ac->ev.addRead = redict_source_add_read; ac->ev.delRead = redict_source_del_read; ac->ev.addWrite = redict_source_add_write; ac->ev.delWrite = redict_source_del_write; ac->ev.cleanup = redict_source_cleanup; ac->ev.data = source; return (GSource *)source; } #endif /* __HIREDICT_GLIB_H__ */ hiredict/adapters/ivykis.h000066400000000000000000000051771460333451400161600ustar00rootroot00000000000000/* * Copyright (c) 2009-2011, Salvatore Sanfilippo * * SPDX-FileCopyrightText: 2024 Hiredict Contributors * SPDX-FileCopyrightText: 2024 Salvatore Sanfilippo * * SPDX-License-Identifier: BSD-3-Clause * SPDX-License-Identifier: LGPL-3.0-or-later * */ #ifndef __HIREDICT_IVYKIS_H__ #define __HIREDICT_IVYKIS_H__ #include #include "../hiredict.h" #include "../async.h" typedef struct redictIvykisEvents { redictAsyncContext *context; struct iv_fd fd; } redictIvykisEvents; static void redictIvykisReadEvent(void *arg) { redictAsyncContext *context = (redictAsyncContext *)arg; redictAsyncHandleRead(context); } static void redictIvykisWriteEvent(void *arg) { redictAsyncContext *context = (redictAsyncContext *)arg; redictAsyncHandleWrite(context); } static void redictIvykisAddRead(void *privdata) { redictIvykisEvents *e = (redictIvykisEvents*)privdata; iv_fd_set_handler_in(&e->fd, redictIvykisReadEvent); } static void redictIvykisDelRead(void *privdata) { redictIvykisEvents *e = (redictIvykisEvents*)privdata; iv_fd_set_handler_in(&e->fd, NULL); } static void redictIvykisAddWrite(void *privdata) { redictIvykisEvents *e = (redictIvykisEvents*)privdata; iv_fd_set_handler_out(&e->fd, redictIvykisWriteEvent); } static void redictIvykisDelWrite(void *privdata) { redictIvykisEvents *e = (redictIvykisEvents*)privdata; iv_fd_set_handler_out(&e->fd, NULL); } static void redictIvykisCleanup(void *privdata) { redictIvykisEvents *e = (redictIvykisEvents*)privdata; iv_fd_unregister(&e->fd); hi_free(e); } static int redictIvykisAttach(redictAsyncContext *ac) { redictContext *c = &(ac->c); redictIvykisEvents *e; /* Nothing should be attached when something is already attached */ if (ac->ev.data != NULL) return REDICT_ERR; /* Create container for context and r/w events */ e = (redictIvykisEvents*)hi_malloc(sizeof(*e)); if (e == NULL) return REDICT_ERR; e->context = ac; /* Register functions to start/stop listening for events */ ac->ev.addRead = redictIvykisAddRead; ac->ev.delRead = redictIvykisDelRead; ac->ev.addWrite = redictIvykisAddWrite; ac->ev.delWrite = redictIvykisDelWrite; ac->ev.cleanup = redictIvykisCleanup; ac->ev.data = e; /* Initialize and install read/write events */ IV_FD_INIT(&e->fd); e->fd.fd = c->fd; e->fd.handler_in = redictIvykisReadEvent; e->fd.handler_out = redictIvykisWriteEvent; e->fd.handler_err = NULL; e->fd.cookie = e->context; iv_fd_register(&e->fd); return REDICT_OK; } #endif hiredict/adapters/libev.h000066400000000000000000000104371460333451400157360ustar00rootroot00000000000000/* * Copyright (c) 2010-2011, Pieter Noordhuis * * All rights reserved. * * SPDX-FileCopyrightText: 2024 Hiredict Contributors * SPDX-FileCopyrightText: 2024 Pieter Noordhuis * * SPDX-License-Identifier: BSD-3-Clause * SPDX-License-Identifier: LGPL-3.0-or-later * */ #ifndef __HIREDICT_LIBEV_H__ #define __HIREDICT_LIBEV_H__ #include #include #include #include "../hiredict.h" #include "../async.h" typedef struct redictLibevEvents { redictAsyncContext *context; struct ev_loop *loop; int reading, writing; ev_io rev, wev; ev_timer timer; } redictLibevEvents; static void redictLibevReadEvent(EV_P_ ev_io *watcher, int revents) { #if EV_MULTIPLICITY ((void)EV_A); #endif ((void)revents); redictLibevEvents *e = (redictLibevEvents*)watcher->data; redictAsyncHandleRead(e->context); } static void redictLibevWriteEvent(EV_P_ ev_io *watcher, int revents) { #if EV_MULTIPLICITY ((void)EV_A); #endif ((void)revents); redictLibevEvents *e = (redictLibevEvents*)watcher->data; redictAsyncHandleWrite(e->context); } static void redictLibevAddRead(void *privdata) { redictLibevEvents *e = (redictLibevEvents*)privdata; #if EV_MULTIPLICITY struct ev_loop *loop = e->loop; #endif if (!e->reading) { e->reading = 1; ev_io_start(EV_A_ &e->rev); } } static void redictLibevDelRead(void *privdata) { redictLibevEvents *e = (redictLibevEvents*)privdata; #if EV_MULTIPLICITY struct ev_loop *loop = e->loop; #endif if (e->reading) { e->reading = 0; ev_io_stop(EV_A_ &e->rev); } } static void redictLibevAddWrite(void *privdata) { redictLibevEvents *e = (redictLibevEvents*)privdata; #if EV_MULTIPLICITY struct ev_loop *loop = e->loop; #endif if (!e->writing) { e->writing = 1; ev_io_start(EV_A_ &e->wev); } } static void redictLibevDelWrite(void *privdata) { redictLibevEvents *e = (redictLibevEvents*)privdata; #if EV_MULTIPLICITY struct ev_loop *loop = e->loop; #endif if (e->writing) { e->writing = 0; ev_io_stop(EV_A_ &e->wev); } } static void redictLibevStopTimer(void *privdata) { redictLibevEvents *e = (redictLibevEvents*)privdata; #if EV_MULTIPLICITY struct ev_loop *loop = e->loop; #endif ev_timer_stop(EV_A_ &e->timer); } static void redictLibevCleanup(void *privdata) { redictLibevEvents *e = (redictLibevEvents*)privdata; redictLibevDelRead(privdata); redictLibevDelWrite(privdata); redictLibevStopTimer(privdata); hi_free(e); } static void redictLibevTimeout(EV_P_ ev_timer *timer, int revents) { #if EV_MULTIPLICITY ((void)EV_A); #endif ((void)revents); redictLibevEvents *e = (redictLibevEvents*)timer->data; redictAsyncHandleTimeout(e->context); } static void redictLibevSetTimeout(void *privdata, struct timeval tv) { redictLibevEvents *e = (redictLibevEvents*)privdata; #if EV_MULTIPLICITY struct ev_loop *loop = e->loop; #endif if (!ev_is_active(&e->timer)) { ev_init(&e->timer, redictLibevTimeout); e->timer.data = e; } e->timer.repeat = tv.tv_sec + tv.tv_usec / 1000000.00; ev_timer_again(EV_A_ &e->timer); } static int redictLibevAttach(EV_P_ redictAsyncContext *ac) { redictContext *c = &(ac->c); redictLibevEvents *e; /* Nothing should be attached when something is already attached */ if (ac->ev.data != NULL) return REDICT_ERR; /* Create container for context and r/w events */ e = (redictLibevEvents*)hi_calloc(1, sizeof(*e)); if (e == NULL) return REDICT_ERR; e->context = ac; #if EV_MULTIPLICITY e->loop = EV_A; #else e->loop = NULL; #endif e->rev.data = e; e->wev.data = e; /* Register functions to start/stop listening for events */ ac->ev.addRead = redictLibevAddRead; ac->ev.delRead = redictLibevDelRead; ac->ev.addWrite = redictLibevAddWrite; ac->ev.delWrite = redictLibevDelWrite; ac->ev.cleanup = redictLibevCleanup; ac->ev.scheduleTimer = redictLibevSetTimeout; ac->ev.data = e; /* Initialize read/write events */ ev_io_init(&e->rev,redictLibevReadEvent,c->fd,EV_READ); ev_io_init(&e->wev,redictLibevWriteEvent,c->fd,EV_WRITE); return REDICT_OK; } #endif hiredict/adapters/libevent.h000066400000000000000000000104251460333451400164420ustar00rootroot00000000000000/* * Copyright (c) 2010-2011, Pieter Noordhuis * * SPDX-FileCopyrightText: 2024 Hiredict Contributors * SPDX-FileCopyrightText: 2024 Pieter Noordhuis * * SPDX-License-Identifier: BSD-3-Clause * SPDX-License-Identifier: LGPL-3.0-or-later * */ #ifndef __HIREDICT_LIBEVENT_H__ #define __HIREDICT_LIBEVENT_H__ #include #include "../hiredict.h" #include "../async.h" #define REDICT_LIBEVENT_DELETED 0x01 #define REDICT_LIBEVENT_ENTERED 0x02 typedef struct redictLibeventEvents { redictAsyncContext *context; struct event *ev; struct event_base *base; struct timeval tv; short flags; short state; } redictLibeventEvents; static void redictLibeventDestroy(redictLibeventEvents *e) { hi_free(e); } static void redictLibeventHandler(evutil_socket_t fd, short event, void *arg) { ((void)fd); redictLibeventEvents *e = (redictLibeventEvents*)arg; e->state |= REDICT_LIBEVENT_ENTERED; #define CHECK_DELETED() if (e->state & REDICT_LIBEVENT_DELETED) {\ redictLibeventDestroy(e);\ return; \ } if ((event & EV_TIMEOUT) && (e->state & REDICT_LIBEVENT_DELETED) == 0) { redictAsyncHandleTimeout(e->context); CHECK_DELETED(); } if ((event & EV_READ) && e->context && (e->state & REDICT_LIBEVENT_DELETED) == 0) { redictAsyncHandleRead(e->context); CHECK_DELETED(); } if ((event & EV_WRITE) && e->context && (e->state & REDICT_LIBEVENT_DELETED) == 0) { redictAsyncHandleWrite(e->context); CHECK_DELETED(); } e->state &= ~REDICT_LIBEVENT_ENTERED; #undef CHECK_DELETED } static void redictLibeventUpdate(void *privdata, short flag, int isRemove) { redictLibeventEvents *e = (redictLibeventEvents *)privdata; const struct timeval *tv = e->tv.tv_sec || e->tv.tv_usec ? &e->tv : NULL; if (isRemove) { if ((e->flags & flag) == 0) { return; } else { e->flags &= ~flag; } } else { if (e->flags & flag) { return; } else { e->flags |= flag; } } event_del(e->ev); event_assign(e->ev, e->base, e->context->c.fd, e->flags | EV_PERSIST, redictLibeventHandler, privdata); event_add(e->ev, tv); } static void redictLibeventAddRead(void *privdata) { redictLibeventUpdate(privdata, EV_READ, 0); } static void redictLibeventDelRead(void *privdata) { redictLibeventUpdate(privdata, EV_READ, 1); } static void redictLibeventAddWrite(void *privdata) { redictLibeventUpdate(privdata, EV_WRITE, 0); } static void redictLibeventDelWrite(void *privdata) { redictLibeventUpdate(privdata, EV_WRITE, 1); } static void redictLibeventCleanup(void *privdata) { redictLibeventEvents *e = (redictLibeventEvents*)privdata; if (!e) { return; } event_del(e->ev); event_free(e->ev); e->ev = NULL; if (e->state & REDICT_LIBEVENT_ENTERED) { e->state |= REDICT_LIBEVENT_DELETED; } else { redictLibeventDestroy(e); } } static void redictLibeventSetTimeout(void *privdata, struct timeval tv) { redictLibeventEvents *e = (redictLibeventEvents *)privdata; short flags = e->flags; e->flags = 0; e->tv = tv; redictLibeventUpdate(e, flags, 0); } static int redictLibeventAttach(redictAsyncContext *ac, struct event_base *base) { redictContext *c = &(ac->c); redictLibeventEvents *e; /* Nothing should be attached when something is already attached */ if (ac->ev.data != NULL) return REDICT_ERR; /* Create container for context and r/w events */ e = (redictLibeventEvents*)hi_calloc(1, sizeof(*e)); if (e == NULL) return REDICT_ERR; e->context = ac; /* Register functions to start/stop listening for events */ ac->ev.addRead = redictLibeventAddRead; ac->ev.delRead = redictLibeventDelRead; ac->ev.addWrite = redictLibeventAddWrite; ac->ev.delWrite = redictLibeventDelWrite; ac->ev.cleanup = redictLibeventCleanup; ac->ev.scheduleTimer = redictLibeventSetTimeout; ac->ev.data = e; /* Initialize and install read/write events */ e->ev = event_new(base, c->fd, EV_READ | EV_WRITE, redictLibeventHandler, e); e->base = base; return REDICT_OK; } #endif hiredict/adapters/libhv.h000066400000000000000000000072611460333451400157420ustar00rootroot00000000000000/* * Copyright (c) 2009-2011, Salvatore Sanfilippo * * SPDX-FileCopyrightText: 2024 Hiredict Contributors * SPDX-FileCopyrightText: 2024 Salvatore Sanfilippo * * SPDX-License-Identifier: BSD-3-Clause * SPDX-License-Identifier: LGPL-3.0-or-later * */ #ifndef __HIREDICT_LIBHV_H__ #define __HIREDICT_LIBHV_H__ #include #include "../hiredict.h" #include "../async.h" typedef struct redictLibhvEvents { hio_t *io; htimer_t *timer; } redictLibhvEvents; static void redictLibhvHandleEvents(hio_t* io) { redictAsyncContext* context = (redictAsyncContext*)hevent_userdata(io); int events = hio_events(io); int revents = hio_revents(io); if (context && (events & HV_READ) && (revents & HV_READ)) { redictAsyncHandleRead(context); } if (context && (events & HV_WRITE) && (revents & HV_WRITE)) { redictAsyncHandleWrite(context); } } static void redictLibhvAddRead(void *privdata) { redictLibhvEvents* events = (redictLibhvEvents*)privdata; hio_add(events->io, redictLibhvHandleEvents, HV_READ); } static void redictLibhvDelRead(void *privdata) { redictLibhvEvents* events = (redictLibhvEvents*)privdata; hio_del(events->io, HV_READ); } static void redictLibhvAddWrite(void *privdata) { redictLibhvEvents* events = (redictLibhvEvents*)privdata; hio_add(events->io, redictLibhvHandleEvents, HV_WRITE); } static void redictLibhvDelWrite(void *privdata) { redictLibhvEvents* events = (redictLibhvEvents*)privdata; hio_del(events->io, HV_WRITE); } static void redictLibhvCleanup(void *privdata) { redictLibhvEvents* events = (redictLibhvEvents*)privdata; if (events->timer) htimer_del(events->timer); hio_close(events->io); hevent_set_userdata(events->io, NULL); hi_free(events); } static void redictLibhvTimeout(htimer_t* timer) { hio_t* io = (hio_t*)hevent_userdata(timer); redictAsyncHandleTimeout((redictAsyncContext*)hevent_userdata(io)); } static void redictLibhvSetTimeout(void *privdata, struct timeval tv) { redictLibhvEvents* events; uint32_t millis; hloop_t* loop; events = (redictLibhvEvents*)privdata; millis = tv.tv_sec * 1000 + tv.tv_usec / 1000; if (millis == 0) { /* Libhv disallows zero'd timers so treat this as a delete or NO OP */ if (events->timer) { htimer_del(events->timer); events->timer = NULL; } } else if (events->timer == NULL) { /* Add new timer */ loop = hevent_loop(events->io); events->timer = htimer_add(loop, redictLibhvTimeout, millis, 1); hevent_set_userdata(events->timer, events->io); } else { /* Update existing timer */ htimer_reset(events->timer, millis); } } static int redictLibhvAttach(redictAsyncContext* ac, hloop_t* loop) { redictContext *c = &(ac->c); redictLibhvEvents *events; hio_t* io = NULL; if (ac->ev.data != NULL) { return REDICT_ERR; } /* Create container struct to keep track of our io and any timer */ events = (redictLibhvEvents*)hi_malloc(sizeof(*events)); if (events == NULL) { return REDICT_ERR; } io = hio_get(loop, c->fd); if (io == NULL) { hi_free(events); return REDICT_ERR; } hevent_set_userdata(io, ac); events->io = io; events->timer = NULL; ac->ev.addRead = redictLibhvAddRead; ac->ev.delRead = redictLibhvDelRead; ac->ev.addWrite = redictLibhvAddWrite; ac->ev.delWrite = redictLibhvDelWrite; ac->ev.cleanup = redictLibhvCleanup; ac->ev.scheduleTimer = redictLibhvSetTimeout; ac->ev.data = events; return REDICT_OK; } #endif hiredict/adapters/libsdevent.h000066400000000000000000000123221460333451400167670ustar00rootroot00000000000000/* * Copyright (c) 2009-2011, Salvatore Sanfilippo * * SPDX-FileCopyrightText: 2024 Hiredict Contributors * SPDX-FileCopyrightText: 2024 Salvatore Sanfilippo * * SPDX-License-Identifier: BSD-3-Clause * SPDX-License-Identifier: LGPL-3.0-or-later * */ #ifndef HIREDICT_LIBSDEVENT_H #define HIREDICT_LIBSDEVENT_H #include #include "../hiredict.h" #include "../async.h" #define REDICT_LIBSDEVENT_DELETED 0x01 #define REDICT_LIBSDEVENT_ENTERED 0x02 typedef struct redictLibsdeventEvents { redictAsyncContext *context; struct sd_event *event; struct sd_event_source *fdSource; struct sd_event_source *timerSource; int fd; short flags; short state; } redictLibsdeventEvents; static void redictLibsdeventDestroy(redictLibsdeventEvents *e) { if (e->fdSource) { e->fdSource = sd_event_source_disable_unref(e->fdSource); } if (e->timerSource) { e->timerSource = sd_event_source_disable_unref(e->timerSource); } sd_event_unref(e->event); hi_free(e); } static int redictLibsdeventTimeoutHandler(sd_event_source *s, uint64_t usec, void *userdata) { ((void)s); ((void)usec); redictLibsdeventEvents *e = (redictLibsdeventEvents*)userdata; redictAsyncHandleTimeout(e->context); return 0; } static int redictLibsdeventHandler(sd_event_source *s, int fd, uint32_t event, void *userdata) { ((void)s); ((void)fd); redictLibsdeventEvents *e = (redictLibsdeventEvents*)userdata; e->state |= REDICT_LIBSDEVENT_ENTERED; #define CHECK_DELETED() if (e->state & REDICT_LIBSDEVENT_DELETED) {\ redictLibsdeventDestroy(e);\ return 0; \ } if ((event & EPOLLIN) && e->context && (e->state & REDICT_LIBSDEVENT_DELETED) == 0) { redictAsyncHandleRead(e->context); CHECK_DELETED(); } if ((event & EPOLLOUT) && e->context && (e->state & REDICT_LIBSDEVENT_DELETED) == 0) { redictAsyncHandleWrite(e->context); CHECK_DELETED(); } e->state &= ~REDICT_LIBSDEVENT_ENTERED; #undef CHECK_DELETED return 0; } static void redictLibsdeventAddRead(void *userdata) { redictLibsdeventEvents *e = (redictLibsdeventEvents*)userdata; if (e->flags & EPOLLIN) { return; } e->flags |= EPOLLIN; if (e->flags & EPOLLOUT) { sd_event_source_set_io_events(e->fdSource, e->flags); } else { sd_event_add_io(e->event, &e->fdSource, e->fd, e->flags, redictLibsdeventHandler, e); } } static void redictLibsdeventDelRead(void *userdata) { redictLibsdeventEvents *e = (redictLibsdeventEvents*)userdata; e->flags &= ~EPOLLIN; if (e->flags) { sd_event_source_set_io_events(e->fdSource, e->flags); } else { e->fdSource = sd_event_source_disable_unref(e->fdSource); } } static void redictLibsdeventAddWrite(void *userdata) { redictLibsdeventEvents *e = (redictLibsdeventEvents*)userdata; if (e->flags & EPOLLOUT) { return; } e->flags |= EPOLLOUT; if (e->flags & EPOLLIN) { sd_event_source_set_io_events(e->fdSource, e->flags); } else { sd_event_add_io(e->event, &e->fdSource, e->fd, e->flags, redictLibsdeventHandler, e); } } static void redictLibsdeventDelWrite(void *userdata) { redictLibsdeventEvents *e = (redictLibsdeventEvents*)userdata; e->flags &= ~EPOLLOUT; if (e->flags) { sd_event_source_set_io_events(e->fdSource, e->flags); } else { e->fdSource = sd_event_source_disable_unref(e->fdSource); } } static void redictLibsdeventCleanup(void *userdata) { redictLibsdeventEvents *e = (redictLibsdeventEvents*)userdata; if (!e) { return; } if (e->state & REDICT_LIBSDEVENT_ENTERED) { e->state |= REDICT_LIBSDEVENT_DELETED; } else { redictLibsdeventDestroy(e); } } static void redictLibsdeventSetTimeout(void *userdata, struct timeval tv) { redictLibsdeventEvents *e = (redictLibsdeventEvents *)userdata; uint64_t usec = tv.tv_sec * 1000000 + tv.tv_usec; if (!e->timerSource) { sd_event_add_time_relative(e->event, &e->timerSource, CLOCK_MONOTONIC, usec, 1, redictLibsdeventTimeoutHandler, e); } else { sd_event_source_set_time_relative(e->timerSource, usec); } } static int redictLibsdeventAttach(redictAsyncContext *ac, struct sd_event *event) { redictContext *c = &(ac->c); redictLibsdeventEvents *e; /* Nothing should be attached when something is already attached */ if (ac->ev.data != NULL) return REDICT_ERR; /* Create container for context and r/w events */ e = (redictLibsdeventEvents*)hi_calloc(1, sizeof(*e)); if (e == NULL) return REDICT_ERR; /* Initialize and increase event refcount */ e->context = ac; e->event = event; e->fd = c->fd; sd_event_ref(event); /* Register functions to start/stop listening for events */ ac->ev.addRead = redictLibsdeventAddRead; ac->ev.delRead = redictLibsdeventDelRead; ac->ev.addWrite = redictLibsdeventAddWrite; ac->ev.delWrite = redictLibsdeventDelWrite; ac->ev.cleanup = redictLibsdeventCleanup; ac->ev.scheduleTimer = redictLibsdeventSetTimeout; ac->ev.data = e; return REDICT_OK; } #endif hiredict/adapters/libuv.h000066400000000000000000000114421460333451400157530ustar00rootroot00000000000000/* * Copyright (c) 2009-2011, Salvatore Sanfilippo * * SPDX-FileCopyrightText: 2024 Hiredict Contributors * SPDX-FileCopyrightText: 2024 Salvatore Sanfilippo * * SPDX-License-Identifier: BSD-3-Clause * SPDX-License-Identifier: LGPL-3.0-or-later * */ #ifndef __HIREDICT_LIBUV_H__ #define __HIREDICT_LIBUV_H__ #include #include #include "../hiredict.h" #include "../async.h" #include typedef struct redictLibuvEvents { redictAsyncContext* context; uv_poll_t handle; uv_timer_t timer; int events; } redictLibuvEvents; static void redictLibuvPoll(uv_poll_t* handle, int status, int events) { redictLibuvEvents* p = (redictLibuvEvents*)handle->data; int ev = (status ? p->events : events); if (p->context != NULL && (ev & UV_READABLE)) { redictAsyncHandleRead(p->context); } if (p->context != NULL && (ev & UV_WRITABLE)) { redictAsyncHandleWrite(p->context); } } static void redictLibuvAddRead(void *privdata) { redictLibuvEvents* p = (redictLibuvEvents*)privdata; if (p->events & UV_READABLE) { return; } p->events |= UV_READABLE; uv_poll_start(&p->handle, p->events, redictLibuvPoll); } static void redictLibuvDelRead(void *privdata) { redictLibuvEvents* p = (redictLibuvEvents*)privdata; p->events &= ~UV_READABLE; if (p->events) { uv_poll_start(&p->handle, p->events, redictLibuvPoll); } else { uv_poll_stop(&p->handle); } } static void redictLibuvAddWrite(void *privdata) { redictLibuvEvents* p = (redictLibuvEvents*)privdata; if (p->events & UV_WRITABLE) { return; } p->events |= UV_WRITABLE; uv_poll_start(&p->handle, p->events, redictLibuvPoll); } static void redictLibuvDelWrite(void *privdata) { redictLibuvEvents* p = (redictLibuvEvents*)privdata; p->events &= ~UV_WRITABLE; if (p->events) { uv_poll_start(&p->handle, p->events, redictLibuvPoll); } else { uv_poll_stop(&p->handle); } } static void on_timer_close(uv_handle_t *handle) { redictLibuvEvents* p = (redictLibuvEvents*)handle->data; p->timer.data = NULL; if (!p->handle.data) { // both timer and handle are closed hi_free(p); } // else, wait for `on_handle_close` } static void on_handle_close(uv_handle_t *handle) { redictLibuvEvents* p = (redictLibuvEvents*)handle->data; p->handle.data = NULL; if (!p->timer.data) { // timer never started, or timer already destroyed hi_free(p); } // else, wait for `on_timer_close` } // libuv removed `status` parameter since v0.11.23 // see: https://github.com/libuv/libuv/blob/v0.11.23/include/uv.h #if (UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR < 11) || \ (UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR == 11 && UV_VERSION_PATCH < 23) static void redictLibuvTimeout(uv_timer_t *timer, int status) { (void)status; // unused #else static void redictLibuvTimeout(uv_timer_t *timer) { #endif redictLibuvEvents *e = (redictLibuvEvents*)timer->data; redictAsyncHandleTimeout(e->context); } static void redictLibuvSetTimeout(void *privdata, struct timeval tv) { redictLibuvEvents* p = (redictLibuvEvents*)privdata; uint64_t millsec = tv.tv_sec * 1000 + tv.tv_usec / 1000.0; if (!p->timer.data) { // timer is uninitialized if (uv_timer_init(p->handle.loop, &p->timer) != 0) { return; } p->timer.data = p; } // updates the timeout if the timer has already started // or start the timer uv_timer_start(&p->timer, redictLibuvTimeout, millsec, 0); } static void redictLibuvCleanup(void *privdata) { redictLibuvEvents* p = (redictLibuvEvents*)privdata; p->context = NULL; // indicate that context might no longer exist if (p->timer.data) { uv_close((uv_handle_t*)&p->timer, on_timer_close); } uv_close((uv_handle_t*)&p->handle, on_handle_close); } static int redictLibuvAttach(redictAsyncContext* ac, uv_loop_t* loop) { redictContext *c = &(ac->c); if (ac->ev.data != NULL) { return REDICT_ERR; } ac->ev.addRead = redictLibuvAddRead; ac->ev.delRead = redictLibuvDelRead; ac->ev.addWrite = redictLibuvAddWrite; ac->ev.delWrite = redictLibuvDelWrite; ac->ev.cleanup = redictLibuvCleanup; ac->ev.scheduleTimer = redictLibuvSetTimeout; redictLibuvEvents* p = (redictLibuvEvents*)hi_malloc(sizeof(*p)); if (p == NULL) return REDICT_ERR; memset(p, 0, sizeof(*p)); if (uv_poll_init_socket(loop, &p->handle, c->fd) != 0) { hi_free(p); return REDICT_ERR; } ac->ev.data = p; p->handle.data = p; p->context = ac; return REDICT_OK; } #endif hiredict/adapters/macosx.h000066400000000000000000000077751460333451400161420ustar00rootroot00000000000000/* * Copyright (c) 2015 Dmitry Bakhvalov * * SPDX-FileCopyrightText: 2024 Hiredict Contributors * SPDX-FileCopyrightText: 2024 Dmitry Bakhvalov * * SPDX-License-Identifier: BSD-3-Clause * SPDX-License-Identifier: LGPL-3.0-or-later * */ #ifndef __HIREDICT_MACOSX_H__ #define __HIREDICT_MACOSX_H__ #include #include "../hiredict.h" #include "../async.h" typedef struct { redictAsyncContext *context; CFSocketRef socketRef; CFRunLoopSourceRef sourceRef; } RedictRunLoop; static int freeRedictRunLoop(RedictRunLoop* redictRunLoop) { if( redictRunLoop != NULL ) { if( redictRunLoop->sourceRef != NULL ) { CFRunLoopSourceInvalidate(redictRunLoop->sourceRef); CFRelease(redictRunLoop->sourceRef); } if( redictRunLoop->socketRef != NULL ) { CFSocketInvalidate(redictRunLoop->socketRef); CFRelease(redictRunLoop->socketRef); } hi_free(redictRunLoop); } return REDICT_ERR; } static void redictMacOSAddRead(void *privdata) { RedictRunLoop *redictRunLoop = (RedictRunLoop*)privdata; CFSocketEnableCallBacks(redictRunLoop->socketRef, kCFSocketReadCallBack); } static void redictMacOSDelRead(void *privdata) { RedictRunLoop *redictRunLoop = (RedictRunLoop*)privdata; CFSocketDisableCallBacks(redictRunLoop->socketRef, kCFSocketReadCallBack); } static void redictMacOSAddWrite(void *privdata) { RedictRunLoop *redictRunLoop = (RedictRunLoop*)privdata; CFSocketEnableCallBacks(redictRunLoop->socketRef, kCFSocketWriteCallBack); } static void redictMacOSDelWrite(void *privdata) { RedictRunLoop *redictRunLoop = (RedictRunLoop*)privdata; CFSocketDisableCallBacks(redictRunLoop->socketRef, kCFSocketWriteCallBack); } static void redictMacOSCleanup(void *privdata) { RedictRunLoop *redictRunLoop = (RedictRunLoop*)privdata; freeRedictRunLoop(redictRunLoop); } static void redictMacOSAsyncCallback(CFSocketRef __unused s, CFSocketCallBackType callbackType, CFDataRef __unused address, const void __unused *data, void *info) { redictAsyncContext* context = (redictAsyncContext*) info; switch (callbackType) { case kCFSocketReadCallBack: redictAsyncHandleRead(context); break; case kCFSocketWriteCallBack: redictAsyncHandleWrite(context); break; default: break; } } static int redictMacOSAttach(redictAsyncContext *redictAsyncCtx, CFRunLoopRef runLoop) { redictContext *redictCtx = &(redictAsyncCtx->c); /* Nothing should be attached when something is already attached */ if( redictAsyncCtx->ev.data != NULL ) return REDICT_ERR; RedictRunLoop* redictRunLoop = (RedictRunLoop*) hi_calloc(1, sizeof(RedictRunLoop)); if (redictRunLoop == NULL) return REDICT_ERR; /* Setup redict stuff */ redictRunLoop->context = redictAsyncCtx; redictAsyncCtx->ev.addRead = redictMacOSAddRead; redictAsyncCtx->ev.delRead = redictMacOSDelRead; redictAsyncCtx->ev.addWrite = redictMacOSAddWrite; redictAsyncCtx->ev.delWrite = redictMacOSDelWrite; redictAsyncCtx->ev.cleanup = redictMacOSCleanup; redictAsyncCtx->ev.data = redictRunLoop; /* Initialize and install read/write events */ CFSocketContext socketCtx = { 0, redictAsyncCtx, NULL, NULL, NULL }; redictRunLoop->socketRef = CFSocketCreateWithNative(NULL, redictCtx->fd, kCFSocketReadCallBack | kCFSocketWriteCallBack, redictMacOSAsyncCallback, &socketCtx); if( !redictRunLoop->socketRef ) return freeRedictRunLoop(redictRunLoop); redictRunLoop->sourceRef = CFSocketCreateRunLoopSource(NULL, redictRunLoop->socketRef, 0); if( !redictRunLoop->sourceRef ) return freeRedictRunLoop(redictRunLoop); CFRunLoopAddSource(runLoop, redictRunLoop->sourceRef, kCFRunLoopDefaultMode); return REDICT_OK; } #endif hiredict/adapters/poll.h000066400000000000000000000130011460333451400155710ustar00rootroot00000000000000/* * Copyright (c) 2009-2011, Salvatore Sanfilippo * * SPDX-FileCopyrightText: 2024 Hiredict Contributors * SPDX-FileCopyrightText: 2024 Salvatore Sanfilippo * * SPDX-License-Identifier: BSD-3-Clause * SPDX-License-Identifier: LGPL-3.0-or-later * */ #ifndef HIREDICT_POLL_H #define HIREDICT_POLL_H #include "../async.h" #include "../sockcompat.h" #include // for memset #include /* Values to return from redictPollTick */ #define REDICT_POLL_HANDLED_READ 1 #define REDICT_POLL_HANDLED_WRITE 2 #define REDICT_POLL_HANDLED_TIMEOUT 4 /* An adapter to allow manual polling of the async context by checking the state * of the underlying file descriptor. Useful in cases where there is no formal * IO event loop but regular ticking can be used, such as in game engines. */ typedef struct redictPollEvents { redictAsyncContext *context; redictFD fd; char reading, writing; char in_tick; char deleted; double deadline; } redictPollEvents; static double redictPollTimevalToDouble(struct timeval *tv) { if (tv == NULL) return 0.0; return tv->tv_sec + tv->tv_usec / 1000000.00; } static double redictPollGetNow(void) { #ifndef _MSC_VER struct timeval tv; gettimeofday(&tv,NULL); return redictPollTimevalToDouble(&tv); #else FILETIME ft; ULARGE_INTEGER li; GetSystemTimeAsFileTime(&ft); li.HighPart = ft.dwHighDateTime; li.LowPart = ft.dwLowDateTime; return (double)li.QuadPart * 1e-7; #endif } /* Poll for io, handling any pending callbacks. The timeout argument can be * positive to wait for a maximum given time for IO, zero to poll, or negative * to wait forever */ static int redictPollTick(redictAsyncContext *ac, double timeout) { int reading, writing; struct pollfd pfd; int handled; int ns; int itimeout; redictPollEvents *e = (redictPollEvents*)ac->ev.data; if (!e) return 0; /* local flags, won't get changed during callbacks */ reading = e->reading; writing = e->writing; if (!reading && !writing) return 0; pfd.fd = e->fd; pfd.events = 0; if (reading) pfd.events = POLLIN; if (writing) pfd.events |= POLLOUT; if (timeout >= 0.0) { itimeout = (int)(timeout * 1000.0); } else { itimeout = -1; } ns = poll(&pfd, 1, itimeout); if (ns < 0) { /* ignore the EINTR error */ if (errno != EINTR) return ns; ns = 0; } handled = 0; e->in_tick = 1; if (ns) { if (reading && (pfd.revents & POLLIN)) { redictAsyncHandleRead(ac); handled |= REDICT_POLL_HANDLED_READ; } /* on Windows, connection failure is indicated with the Exception fdset. * handle it the same as writable. */ if (writing && (pfd.revents & (POLLOUT | POLLERR))) { /* context Read callback may have caused context to be deleted, e.g. by doing an redictAsyncDisconnect() */ if (!e->deleted) { redictAsyncHandleWrite(ac); handled |= REDICT_POLL_HANDLED_WRITE; } } } /* perform timeouts */ if (!e->deleted && e->deadline != 0.0) { double now = redictPollGetNow(); if (now >= e->deadline) { /* deadline has passed. disable timeout and perform callback */ e->deadline = 0.0; redictAsyncHandleTimeout(ac); handled |= REDICT_POLL_HANDLED_TIMEOUT; } } /* do a delayed cleanup if required */ if (e->deleted) hi_free(e); else e->in_tick = 0; return handled; } static void redictPollAddRead(void *data) { redictPollEvents *e = (redictPollEvents*)data; e->reading = 1; } static void redictPollDelRead(void *data) { redictPollEvents *e = (redictPollEvents*)data; e->reading = 0; } static void redictPollAddWrite(void *data) { redictPollEvents *e = (redictPollEvents*)data; e->writing = 1; } static void redictPollDelWrite(void *data) { redictPollEvents *e = (redictPollEvents*)data; e->writing = 0; } static void redictPollCleanup(void *data) { redictPollEvents *e = (redictPollEvents*)data; /* if we are currently processing a tick, postpone deletion */ if (e->in_tick) e->deleted = 1; else hi_free(e); } static void redictPollScheduleTimer(void *data, struct timeval tv) { redictPollEvents *e = (redictPollEvents*)data; double now = redictPollGetNow(); e->deadline = now + redictPollTimevalToDouble(&tv); } static int redictPollAttach(redictAsyncContext *ac) { redictContext *c = &(ac->c); redictPollEvents *e; /* Nothing should be attached when something is already attached */ if (ac->ev.data != NULL) return REDICT_ERR; /* Create container for context and r/w events */ e = (redictPollEvents*)hi_malloc(sizeof(*e)); if (e == NULL) return REDICT_ERR; memset(e, 0, sizeof(*e)); e->context = ac; e->fd = c->fd; e->reading = e->writing = 0; e->in_tick = e->deleted = 0; e->deadline = 0.0; /* Register functions to start/stop listening for events */ ac->ev.addRead = redictPollAddRead; ac->ev.delRead = redictPollDelRead; ac->ev.addWrite = redictPollAddWrite; ac->ev.delWrite = redictPollDelWrite; ac->ev.scheduleTimer = redictPollScheduleTimer; ac->ev.cleanup = redictPollCleanup; ac->ev.data = e; return REDICT_OK; } #endif /* HIREDICT_POLL_H */ hiredict/adapters/qt.h000066400000000000000000000062021460333451400152540ustar00rootroot00000000000000/* * Copyright (C) 2014 Pietro Cerutti * * SPDX-FileCopyrightText: 2024 Hiredict Contributors * SPDX-FileCopyrightText: 2024 Pietro Cerutti * * SPDX-License-Identifier: BSD-3-Clause * SPDX-License-Identifier: LGPL-3.0-or-later * */ #ifndef __HIREDICT_QT_H__ #define __HIREDICT_QT_H__ #include #include "../async.h" static void RedictQtAddRead(void *); static void RedictQtDelRead(void *); static void RedictQtAddWrite(void *); static void RedictQtDelWrite(void *); static void RedictQtCleanup(void *); class RedictQtAdapter : public QObject { Q_OBJECT friend void RedictQtAddRead(void * adapter) { RedictQtAdapter * a = static_cast(adapter); a->addRead(); } friend void RedictQtDelRead(void * adapter) { RedictQtAdapter * a = static_cast(adapter); a->delRead(); } friend void RedictQtAddWrite(void * adapter) { RedictQtAdapter * a = static_cast(adapter); a->addWrite(); } friend void RedictQtDelWrite(void * adapter) { RedictQtAdapter * a = static_cast(adapter); a->delWrite(); } friend void RedictQtCleanup(void * adapter) { RedictQtAdapter * a = static_cast(adapter); a->cleanup(); } public: RedictQtAdapter(QObject * parent = 0) : QObject(parent), m_ctx(0), m_read(0), m_write(0) { } ~RedictQtAdapter() { if (m_ctx != 0) { m_ctx->ev.data = NULL; } } int setContext(redictAsyncContext * ac) { if (ac->ev.data != NULL) { return REDICT_ERR; } m_ctx = ac; m_ctx->ev.data = this; m_ctx->ev.addRead = RedictQtAddRead; m_ctx->ev.delRead = RedictQtDelRead; m_ctx->ev.addWrite = RedictQtAddWrite; m_ctx->ev.delWrite = RedictQtDelWrite; m_ctx->ev.cleanup = RedictQtCleanup; return REDICT_OK; } private: void addRead() { if (m_read) return; m_read = new QSocketNotifier(m_ctx->c.fd, QSocketNotifier::Read, 0); connect(m_read, SIGNAL(activated(int)), this, SLOT(read())); } void delRead() { if (!m_read) return; delete m_read; m_read = 0; } void addWrite() { if (m_write) return; m_write = new QSocketNotifier(m_ctx->c.fd, QSocketNotifier::Write, 0); connect(m_write, SIGNAL(activated(int)), this, SLOT(write())); } void delWrite() { if (!m_write) return; delete m_write; m_write = 0; } void cleanup() { delRead(); delWrite(); } private slots: void read() { redictAsyncHandleRead(m_ctx); } void write() { redictAsyncHandleWrite(m_ctx); } private: redictAsyncContext * m_ctx; QSocketNotifier * m_read; QSocketNotifier * m_write; }; #endif /* !__HIREDICT_QT_H__ */ hiredict/adapters/redictmoduleapi.h000066400000000000000000000106411460333451400200040ustar00rootroot00000000000000/* * Copyright (c) 2009-2011, Salvatore Sanfilippo * * SPDX-FileCopyrightText: 2024 Hiredict Contributors * SPDX-FileCopyrightText: 2024 Salvatore Sanfilippo * * SPDX-License-Identifier: BSD-3-Clause * SPDX-License-Identifier: LGPL-3.0-or-later * */ #ifndef __HIREDICT_REDICTMODULEAPI_H__ #define __HIREDICT_REDICTMODULEAPI_H__ #include "redictmodule.h" #include "../async.h" #include "../hiredict.h" #include typedef struct redictModuleEvents { redictAsyncContext *context; RedictModuleCtx *module_ctx; int fd; int reading, writing; int timer_active; RedictModuleTimerID timer_id; } redictModuleEvents; static inline void redictModuleReadEvent(int fd, void *privdata, int mask) { (void) fd; (void) mask; redictModuleEvents *e = (redictModuleEvents*)privdata; redictAsyncHandleRead(e->context); } static inline void redictModuleWriteEvent(int fd, void *privdata, int mask) { (void) fd; (void) mask; redictModuleEvents *e = (redictModuleEvents*)privdata; redictAsyncHandleWrite(e->context); } static inline void redictModuleAddRead(void *privdata) { redictModuleEvents *e = (redictModuleEvents*)privdata; if (!e->reading) { e->reading = 1; RedictModule_EventLoopAdd(e->fd, REDICTMODULE_EVENTLOOP_READABLE, redictModuleReadEvent, e); } } static inline void redictModuleDelRead(void *privdata) { redictModuleEvents *e = (redictModuleEvents*)privdata; if (e->reading) { e->reading = 0; RedictModule_EventLoopDel(e->fd, REDICTMODULE_EVENTLOOP_READABLE); } } static inline void redictModuleAddWrite(void *privdata) { redictModuleEvents *e = (redictModuleEvents*)privdata; if (!e->writing) { e->writing = 1; RedictModule_EventLoopAdd(e->fd, REDICTMODULE_EVENTLOOP_WRITABLE, redictModuleWriteEvent, e); } } static inline void redictModuleDelWrite(void *privdata) { redictModuleEvents *e = (redictModuleEvents*)privdata; if (e->writing) { e->writing = 0; RedictModule_EventLoopDel(e->fd, REDICTMODULE_EVENTLOOP_WRITABLE); } } static inline void redictModuleStopTimer(void *privdata) { redictModuleEvents *e = (redictModuleEvents*)privdata; if (e->timer_active) { RedictModule_StopTimer(e->module_ctx, e->timer_id, NULL); } e->timer_active = 0; } static inline void redictModuleCleanup(void *privdata) { redictModuleEvents *e = (redictModuleEvents*)privdata; redictModuleDelRead(privdata); redictModuleDelWrite(privdata); redictModuleStopTimer(privdata); hi_free(e); } static inline void redictModuleTimeout(RedictModuleCtx *ctx, void *privdata) { (void) ctx; redictModuleEvents *e = (redictModuleEvents*)privdata; e->timer_active = 0; redictAsyncHandleTimeout(e->context); } static inline void redictModuleSetTimeout(void *privdata, struct timeval tv) { redictModuleEvents* e = (redictModuleEvents*)privdata; redictModuleStopTimer(privdata); mstime_t millis = tv.tv_sec * 1000 + tv.tv_usec / 1000.0; e->timer_id = RedictModule_CreateTimer(e->module_ctx, millis, redictModuleTimeout, e); e->timer_active = 1; } /* Check if Redict is compatible with the adapter. */ static inline int redictModuleCompatibilityCheck(void) { if (!RedictModule_EventLoopAdd || !RedictModule_EventLoopDel || !RedictModule_CreateTimer || !RedictModule_StopTimer) { return REDICT_ERR; } return REDICT_OK; } static inline int redictModuleAttach(redictAsyncContext *ac, RedictModuleCtx *module_ctx) { redictContext *c = &(ac->c); redictModuleEvents *e; /* Nothing should be attached when something is already attached */ if (ac->ev.data != NULL) return REDICT_ERR; /* Create container for context and r/w events */ e = (redictModuleEvents*)hi_malloc(sizeof(*e)); if (e == NULL) return REDICT_ERR; e->context = ac; e->module_ctx = module_ctx; e->fd = c->fd; e->reading = e->writing = 0; e->timer_active = 0; /* Register functions to start/stop listening for events */ ac->ev.addRead = redictModuleAddRead; ac->ev.delRead = redictModuleDelRead; ac->ev.addWrite = redictModuleAddWrite; ac->ev.delWrite = redictModuleDelWrite; ac->ev.cleanup = redictModuleCleanup; ac->ev.scheduleTimer = redictModuleSetTimeout; ac->ev.data = e; return REDICT_OK; } #endif hiredict/alloc.c000066400000000000000000000031641460333451400141160ustar00rootroot00000000000000/* * Copyright (c) 2020, Michael Grunder * * SPDX-FileCopyrightText: 2024 Hiredict Contributors * SPDX-FileCopyrightText: 2024 Michael Grunder * * SPDX-License-Identifier: BSD-3-Clause * SPDX-License-Identifier: LGPL-3.0-or-later * */ #include "fmacros.h" #include "alloc.h" #include #include hiredictAllocFuncs hiredictAllocFns = { .mallocFn = malloc, .callocFn = calloc, .reallocFn = realloc, .strdupFn = strdup, .freeFn = free, }; /* Override hiredict' allocators with ones supplied by the user */ hiredictAllocFuncs hiredictSetAllocators(hiredictAllocFuncs *override) { hiredictAllocFuncs orig = hiredictAllocFns; hiredictAllocFns = *override; return orig; } /* Reset allocators to use libc defaults */ void hiredictResetAllocators(void) { hiredictAllocFns = (hiredictAllocFuncs) { .mallocFn = malloc, .callocFn = calloc, .reallocFn = realloc, .strdupFn = strdup, .freeFn = free, }; } #ifdef _WIN32 void *hi_malloc(size_t size) { return hiredictAllocFns.mallocFn(size); } void *hi_calloc(size_t nmemb, size_t size) { /* Overflow check as the user can specify any arbitrary allocator */ if (SIZE_MAX / size < nmemb) return NULL; return hiredictAllocFns.callocFn(nmemb, size); } void *hi_realloc(void *ptr, size_t size) { return hiredictAllocFns.reallocFn(ptr, size); } char *hi_strdup(const char *str) { return hiredictAllocFns.strdupFn(str); } void hi_free(void *ptr) { hiredictAllocFns.freeFn(ptr); } #endif hiredict/alloc.h000066400000000000000000000034761460333451400141310ustar00rootroot00000000000000/* * Copyright (c) 2020, Michael Grunder * * SPDX-FileCopyrightText: 2024 Hiredict Contributors * SPDX-FileCopyrightText: 2024 Michael Grunder * * SPDX-License-Identifier: BSD-3-Clause * SPDX-License-Identifier: LGPL-3.0-or-later * */ #ifndef HIREDICT_ALLOC_H #define HIREDICT_ALLOC_H #include /* for size_t */ #include #ifdef __cplusplus extern "C" { #endif /* Structure pointing to our actually configured allocators */ typedef struct hiredictAllocFuncs { void *(*mallocFn)(size_t); void *(*callocFn)(size_t,size_t); void *(*reallocFn)(void*,size_t); char *(*strdupFn)(const char*); void (*freeFn)(void*); } hiredictAllocFuncs; hiredictAllocFuncs hiredictSetAllocators(hiredictAllocFuncs *ha); void hiredictResetAllocators(void); #ifndef _WIN32 /* Hiredict' configured allocator function pointer struct */ extern hiredictAllocFuncs hiredictAllocFns; static inline void *hi_malloc(size_t size) { return hiredictAllocFns.mallocFn(size); } static inline void *hi_calloc(size_t nmemb, size_t size) { /* Overflow check as the user can specify any arbitrary allocator */ if (SIZE_MAX / size < nmemb) return NULL; return hiredictAllocFns.callocFn(nmemb, size); } static inline void *hi_realloc(void *ptr, size_t size) { return hiredictAllocFns.reallocFn(ptr, size); } static inline char *hi_strdup(const char *str) { return hiredictAllocFns.strdupFn(str); } static inline void hi_free(void *ptr) { hiredictAllocFns.freeFn(ptr); } #else void *hi_malloc(size_t size); void *hi_calloc(size_t nmemb, size_t size); void *hi_realloc(void *ptr, size_t size); char *hi_strdup(const char *str); void hi_free(void *ptr); #endif #ifdef __cplusplus } #endif #endif /* HIREDICT_ALLOC_H */ hiredict/appveyor.yml000066400000000000000000000013511460333451400152440ustar00rootroot00000000000000# Appveyor configuration file for CI build of hiredict on Windows (under Cygwin) environment: matrix: - CYG_BASH: C:\cygwin64\bin\bash CC: gcc - CYG_BASH: C:\cygwin\bin\bash CC: gcc CFLAGS: -m32 CXXFLAGS: -m32 LDFLAGS: -m32 clone_depth: 1 # Attempt to ensure we don't try to convert line endings to Win32 CRLF as this will cause build to fail init: - git config --global core.autocrlf input # Install needed build dependencies install: - '%CYG_BASH% -lc "cygcheck -dc cygwin"' build_script: - 'echo building...' - '%CYG_BASH% -lc "cd $APPVEYOR_BUILD_FOLDER; exec 0 * Copyright (c) 2010-2011, Pieter Noordhuis * * SPDX-FileCopyrightText: 2024 Hiredict Contributors * SPDX-FileCopyrightText: 2024 Salvatore Sanfilippo * SPDX-FileCopyrightText: 2024 Pieter Noordhuis * * SPDX-License-Identifier: BSD-3-Clause * SPDX-License-Identifier: LGPL-3.0-or-later * */ #include "fmacros.h" #include "alloc.h" #include #include #ifndef _MSC_VER #include #endif #include #include #include #include "async.h" #include "net.h" #include "dict.c" #include "sds.h" #include "win32.h" #include "async_private.h" #ifdef NDEBUG #undef assert #define assert(e) (void)(e) #endif /* Forward declarations of hiredict.c functions */ int __redictAppendCommand(redictContext *c, const char *cmd, size_t len); void __redictSetError(redictContext *c, int type, const char *str); /* Functions managing dictionary of callbacks for pub/sub. */ static unsigned int callbackHash(const void *key) { return dictGenHashFunction((const unsigned char *)key, sdslen((const sds)key)); } static void *callbackValDup(void *privdata, const void *src) { ((void) privdata); redictCallback *dup; dup = hi_malloc(sizeof(*dup)); if (dup == NULL) return NULL; memcpy(dup,src,sizeof(*dup)); return dup; } static int callbackKeyCompare(void *privdata, const void *key1, const void *key2) { int l1, l2; ((void) privdata); l1 = sdslen((const sds)key1); l2 = sdslen((const sds)key2); if (l1 != l2) return 0; return memcmp(key1,key2,l1) == 0; } static void callbackKeyDestructor(void *privdata, void *key) { ((void) privdata); sdsfree((sds)key); } static void callbackValDestructor(void *privdata, void *val) { ((void) privdata); hi_free(val); } static dictType callbackDict = { callbackHash, NULL, callbackValDup, callbackKeyCompare, callbackKeyDestructor, callbackValDestructor }; static redictAsyncContext *redictAsyncInitialize(redictContext *c) { redictAsyncContext *ac; dict *channels = NULL, *patterns = NULL; channels = dictCreate(&callbackDict,NULL); if (channels == NULL) goto oom; patterns = dictCreate(&callbackDict,NULL); if (patterns == NULL) goto oom; ac = hi_realloc(c,sizeof(redictAsyncContext)); if (ac == NULL) goto oom; c = &(ac->c); /* The regular connect functions will always set the flag REDICT_CONNECTED. * For the async API, we want to wait until the first write event is * received up before setting this flag, so reset it here. */ c->flags &= ~REDICT_CONNECTED; ac->err = 0; ac->errstr = NULL; ac->data = NULL; ac->dataCleanup = NULL; ac->ev.data = NULL; ac->ev.addRead = NULL; ac->ev.delRead = NULL; ac->ev.addWrite = NULL; ac->ev.delWrite = NULL; ac->ev.cleanup = NULL; ac->ev.scheduleTimer = NULL; ac->onConnect = NULL; ac->onConnectNC = NULL; ac->onDisconnect = NULL; ac->replies.head = NULL; ac->replies.tail = NULL; ac->sub.replies.head = NULL; ac->sub.replies.tail = NULL; ac->sub.channels = channels; ac->sub.patterns = patterns; ac->sub.pending_unsubs = 0; return ac; oom: if (channels) dictRelease(channels); if (patterns) dictRelease(patterns); return NULL; } /* We want the error field to be accessible directly instead of requiring * an indirection to the redictContext struct. */ static void __redictAsyncCopyError(redictAsyncContext *ac) { if (!ac) return; redictContext *c = &(ac->c); ac->err = c->err; ac->errstr = c->errstr; } redictAsyncContext *redictAsyncConnectWithOptions(const redictOptions *options) { redictOptions myOptions = *options; redictContext *c; redictAsyncContext *ac; /* Clear any erroneously set sync callback and flag that we don't want to * use freeReplyObject by default. */ myOptions.push_cb = NULL; myOptions.options |= REDICT_OPT_NO_PUSH_AUTOFREE; myOptions.options |= REDICT_OPT_NONBLOCK; c = redictConnectWithOptions(&myOptions); if (c == NULL) { return NULL; } ac = redictAsyncInitialize(c); if (ac == NULL) { redictFree(c); return NULL; } /* Set any configured async push handler */ redictAsyncSetPushCallback(ac, myOptions.async_push_cb); __redictAsyncCopyError(ac); return ac; } redictAsyncContext *redictAsyncConnect(const char *ip, int port) { redictOptions options = {0}; REDICT_OPTIONS_SET_TCP(&options, ip, port); return redictAsyncConnectWithOptions(&options); } redictAsyncContext *redictAsyncConnectBind(const char *ip, int port, const char *source_addr) { redictOptions options = {0}; REDICT_OPTIONS_SET_TCP(&options, ip, port); options.endpoint.tcp.source_addr = source_addr; return redictAsyncConnectWithOptions(&options); } redictAsyncContext *redictAsyncConnectBindWithReuse(const char *ip, int port, const char *source_addr) { redictOptions options = {0}; REDICT_OPTIONS_SET_TCP(&options, ip, port); options.options |= REDICT_OPT_REUSEADDR; options.endpoint.tcp.source_addr = source_addr; return redictAsyncConnectWithOptions(&options); } redictAsyncContext *redictAsyncConnectUnix(const char *path) { redictOptions options = {0}; REDICT_OPTIONS_SET_UNIX(&options, path); return redictAsyncConnectWithOptions(&options); } static int redictAsyncSetConnectCallbackImpl(redictAsyncContext *ac, redictConnectCallback *fn, redictConnectCallbackNC *fn_nc) { /* If either are already set, this is an error */ if (ac->onConnect || ac->onConnectNC) return REDICT_ERR; if (fn) { ac->onConnect = fn; } else if (fn_nc) { ac->onConnectNC = fn_nc; } /* The common way to detect an established connection is to wait for * the first write event to be fired. This assumes the related event * library functions are already set. */ _EL_ADD_WRITE(ac); return REDICT_OK; } int redictAsyncSetConnectCallback(redictAsyncContext *ac, redictConnectCallback *fn) { return redictAsyncSetConnectCallbackImpl(ac, fn, NULL); } int redictAsyncSetConnectCallbackNC(redictAsyncContext *ac, redictConnectCallbackNC *fn) { return redictAsyncSetConnectCallbackImpl(ac, NULL, fn); } int redictAsyncSetDisconnectCallback(redictAsyncContext *ac, redictDisconnectCallback *fn) { if (ac->onDisconnect == NULL) { ac->onDisconnect = fn; return REDICT_OK; } return REDICT_ERR; } /* Helper functions to push/shift callbacks */ static int __redictPushCallback(redictCallbackList *list, redictCallback *source) { redictCallback *cb; /* Copy callback from stack to heap */ cb = hi_malloc(sizeof(*cb)); if (cb == NULL) return REDICT_ERR_OOM; if (source != NULL) { memcpy(cb,source,sizeof(*cb)); cb->next = NULL; } /* Store callback in list */ if (list->head == NULL) list->head = cb; if (list->tail != NULL) list->tail->next = cb; list->tail = cb; return REDICT_OK; } static int __redictShiftCallback(redictCallbackList *list, redictCallback *target) { redictCallback *cb = list->head; if (cb != NULL) { list->head = cb->next; if (cb == list->tail) list->tail = NULL; /* Copy callback from heap to stack */ if (target != NULL) memcpy(target,cb,sizeof(*cb)); hi_free(cb); return REDICT_OK; } return REDICT_ERR; } static void __redictRunCallback(redictAsyncContext *ac, redictCallback *cb, redictReply *reply) { redictContext *c = &(ac->c); if (cb->fn != NULL) { c->flags |= REDICT_IN_CALLBACK; cb->fn(ac,reply,cb->privdata); c->flags &= ~REDICT_IN_CALLBACK; } } static void __redictRunPushCallback(redictAsyncContext *ac, redictReply *reply) { if (ac->push_cb != NULL) { ac->c.flags |= REDICT_IN_CALLBACK; ac->push_cb(ac, reply); ac->c.flags &= ~REDICT_IN_CALLBACK; } } static void __redictRunConnectCallback(redictAsyncContext *ac, int status) { if (ac->onConnect == NULL && ac->onConnectNC == NULL) return; if (!(ac->c.flags & REDICT_IN_CALLBACK)) { ac->c.flags |= REDICT_IN_CALLBACK; if (ac->onConnect) { ac->onConnect(ac, status); } else { ac->onConnectNC(ac, status); } ac->c.flags &= ~REDICT_IN_CALLBACK; } else { /* already in callback */ if (ac->onConnect) { ac->onConnect(ac, status); } else { ac->onConnectNC(ac, status); } } } static void __redictRunDisconnectCallback(redictAsyncContext *ac, int status) { if (ac->onDisconnect) { if (!(ac->c.flags & REDICT_IN_CALLBACK)) { ac->c.flags |= REDICT_IN_CALLBACK; ac->onDisconnect(ac, status); ac->c.flags &= ~REDICT_IN_CALLBACK; } else { /* already in callback */ ac->onDisconnect(ac, status); } } } /* Helper function to free the context. */ static void __redictAsyncFree(redictAsyncContext *ac) { redictContext *c = &(ac->c); redictCallback cb; dictIterator it; dictEntry *de; /* Execute pending callbacks with NULL reply. */ while (__redictShiftCallback(&ac->replies,&cb) == REDICT_OK) __redictRunCallback(ac,&cb,NULL); while (__redictShiftCallback(&ac->sub.replies,&cb) == REDICT_OK) __redictRunCallback(ac,&cb,NULL); /* Run subscription callbacks with NULL reply */ if (ac->sub.channels) { dictInitIterator(&it,ac->sub.channels); while ((de = dictNext(&it)) != NULL) __redictRunCallback(ac,dictGetEntryVal(de),NULL); dictRelease(ac->sub.channels); } if (ac->sub.patterns) { dictInitIterator(&it,ac->sub.patterns); while ((de = dictNext(&it)) != NULL) __redictRunCallback(ac,dictGetEntryVal(de),NULL); dictRelease(ac->sub.patterns); } /* Signal event lib to clean up */ _EL_CLEANUP(ac); /* Execute disconnect callback. When redictAsyncFree() initiated destroying * this context, the status will always be REDICT_OK. */ if (c->flags & REDICT_CONNECTED) { int status = ac->err == 0 ? REDICT_OK : REDICT_ERR; if (c->flags & REDICT_FREEING) status = REDICT_OK; __redictRunDisconnectCallback(ac, status); } if (ac->dataCleanup) { ac->dataCleanup(ac->data); } /* Cleanup self */ redictFree(c); } /* Free the async context. When this function is called from a callback, * control needs to be returned to redictProcessCallbacks() before actual * free'ing. To do so, a flag is set on the context which is picked up by * redictProcessCallbacks(). Otherwise, the context is immediately free'd. */ void redictAsyncFree(redictAsyncContext *ac) { if (ac == NULL) return; redictContext *c = &(ac->c); c->flags |= REDICT_FREEING; if (!(c->flags & REDICT_IN_CALLBACK)) __redictAsyncFree(ac); } /* Helper function to make the disconnect happen and clean up. */ void __redictAsyncDisconnect(redictAsyncContext *ac) { redictContext *c = &(ac->c); /* Make sure error is accessible if there is any */ __redictAsyncCopyError(ac); if (ac->err == 0) { /* For clean disconnects, there should be no pending callbacks. */ int ret = __redictShiftCallback(&ac->replies,NULL); assert(ret == REDICT_ERR); } else { /* Disconnection is caused by an error, make sure that pending * callbacks cannot call new commands. */ c->flags |= REDICT_DISCONNECTING; } /* cleanup event library on disconnect. * this is safe to call multiple times */ _EL_CLEANUP(ac); /* For non-clean disconnects, __redictAsyncFree() will execute pending * callbacks with a NULL-reply. */ if (!(c->flags & REDICT_NO_AUTO_FREE)) { __redictAsyncFree(ac); } } /* Tries to do a clean disconnect from Redict, meaning it stops new commands * from being issued, but tries to flush the output buffer and execute * callbacks for all remaining replies. When this function is called from a * callback, there might be more replies and we can safely defer disconnecting * to redictProcessCallbacks(). Otherwise, we can only disconnect immediately * when there are no pending callbacks. */ void redictAsyncDisconnect(redictAsyncContext *ac) { redictContext *c = &(ac->c); c->flags |= REDICT_DISCONNECTING; /** unset the auto-free flag here, because disconnect undoes this */ c->flags &= ~REDICT_NO_AUTO_FREE; if (!(c->flags & REDICT_IN_CALLBACK) && ac->replies.head == NULL) __redictAsyncDisconnect(ac); } static int __redictGetSubscribeCallback(redictAsyncContext *ac, redictReply *reply, redictCallback *dstcb) { redictContext *c = &(ac->c); dict *callbacks; redictCallback *cb = NULL; dictEntry *de; int pvariant; char *stype; sds sname = NULL; /* Match reply with the expected format of a pushed message. * The type and number of elements (3 to 4) are specified at: * TODO: documentation for pubsub#format-of-pushed-messages */ if ((reply->type == REDICT_REPLY_ARRAY && !(c->flags & REDICT_SUPPORTS_PUSH) && reply->elements >= 3) || reply->type == REDICT_REPLY_PUSH) { assert(reply->element[0]->type == REDICT_REPLY_STRING); stype = reply->element[0]->str; pvariant = (tolower(stype[0]) == 'p') ? 1 : 0; if (pvariant) callbacks = ac->sub.patterns; else callbacks = ac->sub.channels; /* Locate the right callback */ if (reply->element[1]->type == REDICT_REPLY_STRING) { sname = sdsnewlen(reply->element[1]->str,reply->element[1]->len); if (sname == NULL) goto oom; if ((de = dictFind(callbacks,sname)) != NULL) { cb = dictGetEntryVal(de); memcpy(dstcb,cb,sizeof(*dstcb)); } } /* If this is an subscribe reply decrease pending counter. */ if (strcasecmp(stype+pvariant,"subscribe") == 0) { assert(cb != NULL); cb->pending_subs -= 1; } else if (strcasecmp(stype+pvariant,"unsubscribe") == 0) { if (cb == NULL) ac->sub.pending_unsubs -= 1; else if (cb->pending_subs == 0) dictDelete(callbacks,sname); /* If this was the last unsubscribe message, revert to * non-subscribe mode. */ assert(reply->element[2]->type == REDICT_REPLY_INTEGER); /* Unset subscribed flag only when no pipelined pending subscribe * or pending unsubscribe replies. */ if (reply->element[2]->integer == 0 && dictSize(ac->sub.channels) == 0 && dictSize(ac->sub.patterns) == 0 && ac->sub.pending_unsubs == 0) { c->flags &= ~REDICT_SUBSCRIBED; /* Move ongoing regular command callbacks. */ redictCallback cb; while (__redictShiftCallback(&ac->sub.replies,&cb) == REDICT_OK) { __redictPushCallback(&ac->replies,&cb); } } } sdsfree(sname); } else { /* Shift callback for pending command in subscribed context. */ __redictShiftCallback(&ac->sub.replies,dstcb); } return REDICT_OK; oom: __redictSetError(&(ac->c), REDICT_ERR_OOM, "Out of memory"); __redictAsyncCopyError(ac); return REDICT_ERR; } #define redictIsSpontaneousPushReply(r) \ (redictIsPushReply(r) && !redictIsSubscribeReply(r)) static int redictIsSubscribeReply(redictReply *reply) { char *str; size_t len, off; /* We will always have at least one string with the subscribe/message type */ if (reply->elements < 1 || reply->element[0]->type != REDICT_REPLY_STRING || reply->element[0]->len < sizeof("message") - 1) { return 0; } /* Get the string/len moving past 'p' if needed */ off = tolower(reply->element[0]->str[0]) == 'p'; str = reply->element[0]->str + off; len = reply->element[0]->len - off; return !strncasecmp(str, "subscribe", len) || !strncasecmp(str, "message", len) || !strncasecmp(str, "unsubscribe", len); } void redictProcessCallbacks(redictAsyncContext *ac) { redictContext *c = &(ac->c); void *reply = NULL; int status; while((status = redictGetReply(c,&reply)) == REDICT_OK) { if (reply == NULL) { /* When the connection is being disconnected and there are * no more replies, this is the cue to really disconnect. */ if (c->flags & REDICT_DISCONNECTING && sdslen(c->obuf) == 0 && ac->replies.head == NULL) { __redictAsyncDisconnect(ac); return; } /* When the connection is not being disconnected, simply stop * trying to get replies and wait for the next loop tick. */ break; } /* Keep track of push message support for subscribe handling */ if (redictIsPushReply(reply)) c->flags |= REDICT_SUPPORTS_PUSH; /* Send any non-subscribe related PUSH messages to our PUSH handler * while allowing subscribe related PUSH messages to pass through. * This allows existing code to be backward compatible and work in * either RESP2 or RESP3 mode. */ if (redictIsSpontaneousPushReply(reply)) { __redictRunPushCallback(ac, reply); c->reader->fn->freeObject(reply); continue; } /* Even if the context is subscribed, pending regular * callbacks will get a reply before pub/sub messages arrive. */ redictCallback cb = {NULL, NULL, 0, 0, NULL}; if (__redictShiftCallback(&ac->replies,&cb) != REDICT_OK) { /* * A spontaneous reply in a not-subscribed context can be the error * reply that is sent when a new connection exceeds the maximum * number of allowed connections on the server side. * * This is seen as an error instead of a regular reply because the * server closes the connection after sending it. * * To prevent the error from being overwritten by an EOF error the * connection is closed here. See issue #43. * * Another possibility is that the server is loading its dataset. * In this case we also want to close the connection, and have the * user wait until the server is ready to take our request. */ if (((redictReply*)reply)->type == REDICT_REPLY_ERROR) { c->err = REDICT_ERR_OTHER; snprintf(c->errstr,sizeof(c->errstr),"%s",((redictReply*)reply)->str); c->reader->fn->freeObject(reply); __redictAsyncDisconnect(ac); return; } /* No more regular callbacks and no errors, the context *must* be subscribed. */ assert(c->flags & REDICT_SUBSCRIBED); if (c->flags & REDICT_SUBSCRIBED) __redictGetSubscribeCallback(ac,reply,&cb); } if (cb.fn != NULL) { __redictRunCallback(ac,&cb,reply); if (!(c->flags & REDICT_NO_AUTO_FREE_REPLIES)){ c->reader->fn->freeObject(reply); } /* Proceed with free'ing when redictAsyncFree() was called. */ if (c->flags & REDICT_FREEING) { __redictAsyncFree(ac); return; } } else { /* No callback for this reply. This can either be a NULL callback, * or there were no callbacks to begin with. Either way, don't * abort with an error, but simply ignore it because the client * doesn't know what the server will spit out over the wire. */ c->reader->fn->freeObject(reply); } /* If in monitor mode, repush the callback */ if (c->flags & REDICT_MONITORING) { __redictPushCallback(&ac->replies,&cb); } } /* Disconnect when there was an error reading the reply */ if (status != REDICT_OK) __redictAsyncDisconnect(ac); } static void __redictAsyncHandleConnectFailure(redictAsyncContext *ac) { __redictRunConnectCallback(ac, REDICT_ERR); __redictAsyncDisconnect(ac); } /* Internal helper function to detect socket status the first time a read or * write event fires. When connecting was not successful, the connect callback * is called with a REDICT_ERR status and the context is free'd. */ static int __redictAsyncHandleConnect(redictAsyncContext *ac) { int completed = 0; redictContext *c = &(ac->c); if (redictCheckConnectDone(c, &completed) == REDICT_ERR) { /* Error! */ if (redictCheckSocketError(c) == REDICT_ERR) __redictAsyncCopyError(ac); __redictAsyncHandleConnectFailure(ac); return REDICT_ERR; } else if (completed == 1) { /* connected! */ if (c->connection_type == REDICT_CONN_TCP && redictSetTcpNoDelay(c) == REDICT_ERR) { __redictAsyncHandleConnectFailure(ac); return REDICT_ERR; } /* flag us as fully connect, but allow the callback * to disconnect. For that reason, permit the function * to delete the context here after callback return. */ c->flags |= REDICT_CONNECTED; __redictRunConnectCallback(ac, REDICT_OK); if ((ac->c.flags & REDICT_DISCONNECTING)) { redictAsyncDisconnect(ac); return REDICT_ERR; } else if ((ac->c.flags & REDICT_FREEING)) { redictAsyncFree(ac); return REDICT_ERR; } return REDICT_OK; } else { return REDICT_OK; } } void redictAsyncRead(redictAsyncContext *ac) { redictContext *c = &(ac->c); if (redictBufferRead(c) == REDICT_ERR) { __redictAsyncDisconnect(ac); } else { /* Always re-schedule reads */ _EL_ADD_READ(ac); redictProcessCallbacks(ac); } } /* This function should be called when the socket is readable. * It processes all replies that can be read and executes their callbacks. */ void redictAsyncHandleRead(redictAsyncContext *ac) { redictContext *c = &(ac->c); /* must not be called from a callback */ assert(!(c->flags & REDICT_IN_CALLBACK)); if (!(c->flags & REDICT_CONNECTED)) { /* Abort connect was not successful. */ if (__redictAsyncHandleConnect(ac) != REDICT_OK) return; /* Try again later when the context is still not connected. */ if (!(c->flags & REDICT_CONNECTED)) return; } c->funcs->async_read(ac); } void redictAsyncWrite(redictAsyncContext *ac) { redictContext *c = &(ac->c); int done = 0; if (redictBufferWrite(c,&done) == REDICT_ERR) { __redictAsyncDisconnect(ac); } else { /* Continue writing when not done, stop writing otherwise */ if (!done) _EL_ADD_WRITE(ac); else _EL_DEL_WRITE(ac); /* Always schedule reads after writes */ _EL_ADD_READ(ac); } } void redictAsyncHandleWrite(redictAsyncContext *ac) { redictContext *c = &(ac->c); /* must not be called from a callback */ assert(!(c->flags & REDICT_IN_CALLBACK)); if (!(c->flags & REDICT_CONNECTED)) { /* Abort connect was not successful. */ if (__redictAsyncHandleConnect(ac) != REDICT_OK) return; /* Try again later when the context is still not connected. */ if (!(c->flags & REDICT_CONNECTED)) return; } c->funcs->async_write(ac); } void redictAsyncHandleTimeout(redictAsyncContext *ac) { redictContext *c = &(ac->c); redictCallback cb; /* must not be called from a callback */ assert(!(c->flags & REDICT_IN_CALLBACK)); if ((c->flags & REDICT_CONNECTED)) { if (ac->replies.head == NULL && ac->sub.replies.head == NULL) { /* Nothing to do - just an idle timeout */ return; } if (!ac->c.command_timeout || (!ac->c.command_timeout->tv_sec && !ac->c.command_timeout->tv_usec)) { /* A belated connect timeout arriving, ignore */ return; } } if (!c->err) { __redictSetError(c, REDICT_ERR_TIMEOUT, "Timeout"); __redictAsyncCopyError(ac); } if (!(c->flags & REDICT_CONNECTED)) { __redictRunConnectCallback(ac, REDICT_ERR); } while (__redictShiftCallback(&ac->replies, &cb) == REDICT_OK) { __redictRunCallback(ac, &cb, NULL); } /** * TODO: Don't automatically sever the connection, * rather, allow to ignore responses before the queue is clear */ __redictAsyncDisconnect(ac); } /* Sets a pointer to the first argument and its length starting at p. Returns * the number of bytes to skip to get to the following argument. */ static const char *nextArgument(const char *start, const char **str, size_t *len) { const char *p = start; if (p[0] != '$') { p = strchr(p,'$'); if (p == NULL) return NULL; } *len = (int)strtol(p+1,NULL,10); p = strchr(p,'\r'); assert(p); *str = p+2; return p+2+(*len)+2; } /* Helper function for the redictAsyncCommand* family of functions. Writes a * formatted command to the output buffer and registers the provided callback * function with the context. */ static int __redictAsyncCommand(redictAsyncContext *ac, redictCallbackFn *fn, void *privdata, const char *cmd, size_t len) { redictContext *c = &(ac->c); redictCallback cb; struct dict *cbdict; dictIterator it; dictEntry *de; redictCallback *existcb; int pvariant, hasnext; const char *cstr, *astr; size_t clen, alen; const char *p; sds sname; int ret; /* Don't accept new commands when the connection is about to be closed. */ if (c->flags & (REDICT_DISCONNECTING | REDICT_FREEING)) return REDICT_ERR; /* Setup callback */ cb.fn = fn; cb.privdata = privdata; cb.pending_subs = 1; cb.unsubscribe_sent = 0; /* Find out which command will be appended. */ p = nextArgument(cmd,&cstr,&clen); assert(p != NULL); hasnext = (p[0] == '$'); pvariant = (tolower(cstr[0]) == 'p') ? 1 : 0; cstr += pvariant; clen -= pvariant; if (hasnext && strncasecmp(cstr,"subscribe\r\n",11) == 0) { c->flags |= REDICT_SUBSCRIBED; /* Add every channel/pattern to the list of subscription callbacks. */ while ((p = nextArgument(p,&astr,&alen)) != NULL) { sname = sdsnewlen(astr,alen); if (sname == NULL) goto oom; if (pvariant) cbdict = ac->sub.patterns; else cbdict = ac->sub.channels; de = dictFind(cbdict,sname); if (de != NULL) { existcb = dictGetEntryVal(de); cb.pending_subs = existcb->pending_subs + 1; } ret = dictReplace(cbdict,sname,&cb); if (ret == 0) sdsfree(sname); } } else if (strncasecmp(cstr,"unsubscribe\r\n",13) == 0) { /* It is only useful to call (P)UNSUBSCRIBE when the context is * subscribed to one or more channels or patterns. */ if (!(c->flags & REDICT_SUBSCRIBED)) return REDICT_ERR; if (pvariant) cbdict = ac->sub.patterns; else cbdict = ac->sub.channels; if (hasnext) { /* Send an unsubscribe with specific channels/patterns. * Bookkeeping the number of expected replies */ while ((p = nextArgument(p,&astr,&alen)) != NULL) { sname = sdsnewlen(astr,alen); if (sname == NULL) goto oom; de = dictFind(cbdict,sname); if (de != NULL) { existcb = dictGetEntryVal(de); if (existcb->unsubscribe_sent == 0) existcb->unsubscribe_sent = 1; else /* Already sent, reply to be ignored */ ac->sub.pending_unsubs += 1; } else { /* Not subscribed to, reply to be ignored */ ac->sub.pending_unsubs += 1; } sdsfree(sname); } } else { /* Send an unsubscribe without specific channels/patterns. * Bookkeeping the number of expected replies */ int no_subs = 1; dictInitIterator(&it,cbdict); while ((de = dictNext(&it)) != NULL) { existcb = dictGetEntryVal(de); if (existcb->unsubscribe_sent == 0) { existcb->unsubscribe_sent = 1; no_subs = 0; } } /* Unsubscribing to all channels/patterns, where none is * subscribed to, results in a single reply to be ignored. */ if (no_subs == 1) ac->sub.pending_unsubs += 1; } /* (P)UNSUBSCRIBE does not have its own response: every channel or * pattern that is unsubscribed will receive a message. This means we * should not append a callback function for this command. */ } else if (strncasecmp(cstr,"monitor\r\n",9) == 0) { /* Set monitor flag and push callback */ c->flags |= REDICT_MONITORING; if (__redictPushCallback(&ac->replies,&cb) != REDICT_OK) goto oom; } else { if (c->flags & REDICT_SUBSCRIBED) { if (__redictPushCallback(&ac->sub.replies,&cb) != REDICT_OK) goto oom; } else { if (__redictPushCallback(&ac->replies,&cb) != REDICT_OK) goto oom; } } __redictAppendCommand(c,cmd,len); /* Always schedule a write when the write buffer is non-empty */ _EL_ADD_WRITE(ac); return REDICT_OK; oom: __redictSetError(&(ac->c), REDICT_ERR_OOM, "Out of memory"); __redictAsyncCopyError(ac); return REDICT_ERR; } int redictvAsyncCommand(redictAsyncContext *ac, redictCallbackFn *fn, void *privdata, const char *format, va_list ap) { char *cmd; int len; int status; len = redictvFormatCommand(&cmd,format,ap); /* We don't want to pass -1 or -2 to future functions as a length. */ if (len < 0) return REDICT_ERR; status = __redictAsyncCommand(ac,fn,privdata,cmd,len); hi_free(cmd); return status; } int redictAsyncCommand(redictAsyncContext *ac, redictCallbackFn *fn, void *privdata, const char *format, ...) { va_list ap; int status; va_start(ap,format); status = redictvAsyncCommand(ac,fn,privdata,format,ap); va_end(ap); return status; } int redictAsyncCommandArgv(redictAsyncContext *ac, redictCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen) { sds cmd; long long len; int status; len = redictFormatSdsCommandArgv(&cmd,argc,argv,argvlen); if (len < 0) return REDICT_ERR; status = __redictAsyncCommand(ac,fn,privdata,cmd,len); sdsfree(cmd); return status; } int redictAsyncFormattedCommand(redictAsyncContext *ac, redictCallbackFn *fn, void *privdata, const char *cmd, size_t len) { int status = __redictAsyncCommand(ac,fn,privdata,cmd,len); return status; } redictAsyncPushFn *redictAsyncSetPushCallback(redictAsyncContext *ac, redictAsyncPushFn *fn) { redictAsyncPushFn *old = ac->push_cb; ac->push_cb = fn; return old; } int redictAsyncSetTimeout(redictAsyncContext *ac, struct timeval tv) { if (!ac->c.command_timeout) { ac->c.command_timeout = hi_calloc(1, sizeof(tv)); if (ac->c.command_timeout == NULL) { __redictSetError(&ac->c, REDICT_ERR_OOM, "Out of memory"); __redictAsyncCopyError(ac); return REDICT_ERR; } } if (tv.tv_sec != ac->c.command_timeout->tv_sec || tv.tv_usec != ac->c.command_timeout->tv_usec) { *ac->c.command_timeout = tv; } return REDICT_OK; } hiredict/async.h000066400000000000000000000120431460333451400141420ustar00rootroot00000000000000/* * Copyright (c) 2009-2011, Salvatore Sanfilippo * Copyright (c) 2010-2011, Pieter Noordhuis * * SPDX-FileCopyrightText: 2024 Hiredict Contributors * SPDX-FileCopyrightText: 2024 Salvatore Sanfilippo * SPDX-FileCopyrightText: 2024 Pieter Noordhuis * * SPDX-License-Identifier: BSD-3-Clause * SPDX-License-Identifier: LGPL-3.0-or-later * */ #ifndef __HIREDICT_ASYNC_H #define __HIREDICT_ASYNC_H #include "hiredict.h" #ifdef __cplusplus extern "C" { #endif struct redictAsyncContext; /* need forward declaration of redictAsyncContext */ struct dict; /* dictionary header is included in async.c */ /* Reply callback prototype and container */ typedef void (redictCallbackFn)(struct redictAsyncContext*, void*, void*); typedef struct redictCallback { struct redictCallback *next; /* simple singly linked list */ redictCallbackFn *fn; int pending_subs; int unsubscribe_sent; void *privdata; } redictCallback; /* List of callbacks for either regular replies or pub/sub */ typedef struct redictCallbackList { redictCallback *head, *tail; } redictCallbackList; /* Connection callback prototypes */ typedef void (redictDisconnectCallback)(const struct redictAsyncContext*, int status); typedef void (redictConnectCallback)(const struct redictAsyncContext*, int status); typedef void (redictConnectCallbackNC)(struct redictAsyncContext*, int status); typedef void (redictTimerCallback)(void *timer, void *privdata); /* Context for an async connection to Redict */ typedef struct redictAsyncContext { /* Hold the regular context, so it can be realloc'ed. */ redictContext c; /* Setup error flags so they can be used directly. */ int err; char *errstr; /* Not used by hiredict */ void *data; void (*dataCleanup)(void *privdata); /* Event library data and hooks */ struct { void *data; /* Hooks that are called when the library expects to start * reading/writing. These functions should be idempotent. */ void (*addRead)(void *privdata); void (*delRead)(void *privdata); void (*addWrite)(void *privdata); void (*delWrite)(void *privdata); void (*cleanup)(void *privdata); void (*scheduleTimer)(void *privdata, struct timeval tv); } ev; /* Called when either the connection is terminated due to an error or per * user request. The status is set accordingly (REDICT_OK, REDICT_ERR). */ redictDisconnectCallback *onDisconnect; /* Called when the first write event was received. */ redictConnectCallback *onConnect; redictConnectCallbackNC *onConnectNC; /* Regular command callbacks */ redictCallbackList replies; /* Address used for connect() */ struct sockaddr *saddr; size_t addrlen; /* Subscription callbacks */ struct { redictCallbackList replies; struct dict *channels; struct dict *patterns; int pending_unsubs; } sub; /* Any configured RESP3 PUSH handler */ redictAsyncPushFn *push_cb; } redictAsyncContext; /* Functions that proxy to hiredict */ redictAsyncContext *redictAsyncConnectWithOptions(const redictOptions *options); redictAsyncContext *redictAsyncConnect(const char *ip, int port); redictAsyncContext *redictAsyncConnectBind(const char *ip, int port, const char *source_addr); redictAsyncContext *redictAsyncConnectBindWithReuse(const char *ip, int port, const char *source_addr); redictAsyncContext *redictAsyncConnectUnix(const char *path); int redictAsyncSetConnectCallback(redictAsyncContext *ac, redictConnectCallback *fn); int redictAsyncSetConnectCallbackNC(redictAsyncContext *ac, redictConnectCallbackNC *fn); int redictAsyncSetDisconnectCallback(redictAsyncContext *ac, redictDisconnectCallback *fn); redictAsyncPushFn *redictAsyncSetPushCallback(redictAsyncContext *ac, redictAsyncPushFn *fn); int redictAsyncSetTimeout(redictAsyncContext *ac, struct timeval tv); void redictAsyncDisconnect(redictAsyncContext *ac); void redictAsyncFree(redictAsyncContext *ac); /* Handle read/write events */ void redictAsyncHandleRead(redictAsyncContext *ac); void redictAsyncHandleWrite(redictAsyncContext *ac); void redictAsyncHandleTimeout(redictAsyncContext *ac); void redictAsyncRead(redictAsyncContext *ac); void redictAsyncWrite(redictAsyncContext *ac); /* Command functions for an async context. Write the command to the * output buffer and register the provided callback. */ int redictvAsyncCommand(redictAsyncContext *ac, redictCallbackFn *fn, void *privdata, const char *format, va_list ap); int redictAsyncCommand(redictAsyncContext *ac, redictCallbackFn *fn, void *privdata, const char *format, ...); int redictAsyncCommandArgv(redictAsyncContext *ac, redictCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen); int redictAsyncFormattedCommand(redictAsyncContext *ac, redictCallbackFn *fn, void *privdata, const char *cmd, size_t len); #ifdef __cplusplus } #endif #endif hiredict/async_private.h000066400000000000000000000041611460333451400156760ustar00rootroot00000000000000/* * Copyright (c) 2009-2011, Salvatore Sanfilippo * Copyright (c) 2010-2011, Pieter Noordhuis * * SPDX-FileCopyrightText: 2024 Hiredict Contributors * SPDX-FileCopyrightText: 2024 Salvatore Sanfilippo * SPDX-FileCopyrightText: 2024 Pieter Noordhuis * * SPDX-License-Identifier: BSD-3-Clause * SPDX-License-Identifier: LGPL-3.0-or-later * */ #ifndef __HIREDICT_ASYNC_PRIVATE_H #define __HIREDICT_ASYNC_PRIVATE_H #define _EL_ADD_READ(ctx) \ do { \ refreshTimeout(ctx); \ if ((ctx)->ev.addRead) (ctx)->ev.addRead((ctx)->ev.data); \ } while (0) #define _EL_DEL_READ(ctx) do { \ if ((ctx)->ev.delRead) (ctx)->ev.delRead((ctx)->ev.data); \ } while(0) #define _EL_ADD_WRITE(ctx) \ do { \ refreshTimeout(ctx); \ if ((ctx)->ev.addWrite) (ctx)->ev.addWrite((ctx)->ev.data); \ } while (0) #define _EL_DEL_WRITE(ctx) do { \ if ((ctx)->ev.delWrite) (ctx)->ev.delWrite((ctx)->ev.data); \ } while(0) #define _EL_CLEANUP(ctx) do { \ if ((ctx)->ev.cleanup) (ctx)->ev.cleanup((ctx)->ev.data); \ ctx->ev.cleanup = NULL; \ } while(0) static inline void refreshTimeout(redictAsyncContext *ctx) { #define REDICT_TIMER_ISSET(tvp) \ (tvp && ((tvp)->tv_sec || (tvp)->tv_usec)) #define REDICT_EL_TIMER(ac, tvp) \ if ((ac)->ev.scheduleTimer && REDICT_TIMER_ISSET(tvp)) { \ (ac)->ev.scheduleTimer((ac)->ev.data, *(tvp)); \ } if (ctx->c.flags & REDICT_CONNECTED) { REDICT_EL_TIMER(ctx, ctx->c.command_timeout); } else { REDICT_EL_TIMER(ctx, ctx->c.connect_timeout); } } void __redictAsyncDisconnect(redictAsyncContext *ac); void redictProcessCallbacks(redictAsyncContext *ac); #endif /* __HIREDICT_ASYNC_PRIVATE_H */ hiredict/dict.c000066400000000000000000000222111460333451400137410ustar00rootroot00000000000000/* Hash table implementation. * * This file implements in memory hash tables with insert/del/replace/find/ * get-random-element operations. Hash tables will auto resize if needed * tables of power of two in size are used, collisions are handled by * chaining. See the source code for more information... :) * * Copyright (c) 2006-2010, Salvatore Sanfilippo * * SPDX-FileCopyrightText: 2024 Hiredict Contributors * SPDX-FileCopyrightText: 2024 Salvatore Sanfilippo * * SPDX-License-Identifier: BSD-3-Clause * SPDX-License-Identifier: LGPL-3.0-or-later * */ #include "fmacros.h" #include "alloc.h" #include #include #include #include "dict.h" /* -------------------------- private prototypes ---------------------------- */ static int _dictExpandIfNeeded(dict *ht); static unsigned long _dictNextPower(unsigned long size); static int _dictKeyIndex(dict *ht, const void *key); static int _dictInit(dict *ht, dictType *type, void *privDataPtr); /* -------------------------- hash functions -------------------------------- */ /* Generic hash function (a popular one from Bernstein). * I tested a few and this was the best. */ static unsigned int dictGenHashFunction(const unsigned char *buf, int len) { unsigned int hash = 5381; while (len--) hash = ((hash << 5) + hash) + (*buf++); /* hash * 33 + c */ return hash; } /* ----------------------------- API implementation ------------------------- */ /* Reset an hashtable already initialized with ht_init(). * NOTE: This function should only called by ht_destroy(). */ static void _dictReset(dict *ht) { ht->table = NULL; ht->size = 0; ht->sizemask = 0; ht->used = 0; } /* Create a new hash table */ static dict *dictCreate(dictType *type, void *privDataPtr) { dict *ht = hi_malloc(sizeof(*ht)); if (ht == NULL) return NULL; _dictInit(ht,type,privDataPtr); return ht; } /* Initialize the hash table */ static int _dictInit(dict *ht, dictType *type, void *privDataPtr) { _dictReset(ht); ht->type = type; ht->privdata = privDataPtr; return DICT_OK; } /* Expand or create the hashtable */ static int dictExpand(dict *ht, unsigned long size) { dict n; /* the new hashtable */ unsigned long realsize = _dictNextPower(size), i; /* the size is invalid if it is smaller than the number of * elements already inside the hashtable */ if (ht->used > size) return DICT_ERR; _dictInit(&n, ht->type, ht->privdata); n.size = realsize; n.sizemask = realsize-1; n.table = hi_calloc(realsize,sizeof(dictEntry*)); if (n.table == NULL) return DICT_ERR; /* Copy all the elements from the old to the new table: * note that if the old hash table is empty ht->size is zero, * so dictExpand just creates an hash table. */ n.used = ht->used; for (i = 0; i < ht->size && ht->used > 0; i++) { dictEntry *he, *nextHe; if (ht->table[i] == NULL) continue; /* For each hash entry on this slot... */ he = ht->table[i]; while(he) { unsigned int h; nextHe = he->next; /* Get the new element index */ h = dictHashKey(ht, he->key) & n.sizemask; he->next = n.table[h]; n.table[h] = he; ht->used--; /* Pass to the next element */ he = nextHe; } } assert(ht->used == 0); hi_free(ht->table); /* Remap the new hashtable in the old */ *ht = n; return DICT_OK; } /* Add an element to the target hash table */ static int dictAdd(dict *ht, void *key, void *val) { int index; dictEntry *entry; /* Get the index of the new element, or -1 if * the element already exists. */ if ((index = _dictKeyIndex(ht, key)) == -1) return DICT_ERR; /* Allocates the memory and stores key */ entry = hi_malloc(sizeof(*entry)); if (entry == NULL) return DICT_ERR; entry->next = ht->table[index]; ht->table[index] = entry; /* Set the hash entry fields. */ dictSetHashKey(ht, entry, key); dictSetHashVal(ht, entry, val); ht->used++; return DICT_OK; } /* Add an element, discarding the old if the key already exists. * Return 1 if the key was added from scratch, 0 if there was already an * element with such key and dictReplace() just performed a value update * operation. */ static int dictReplace(dict *ht, void *key, void *val) { dictEntry *entry, auxentry; /* Try to add the element. If the key * does not exists dictAdd will succeed. */ if (dictAdd(ht, key, val) == DICT_OK) return 1; /* It already exists, get the entry */ entry = dictFind(ht, key); if (entry == NULL) return 0; /* Free the old value and set the new one */ /* Set the new value and free the old one. Note that it is important * to do that in this order, as the value may just be exactly the same * as the previous one. In this context, think to reference counting, * you want to increment (set), and then decrement (free), and not the * reverse. */ auxentry = *entry; dictSetHashVal(ht, entry, val); dictFreeEntryVal(ht, &auxentry); return 0; } /* Search and remove an element */ static int dictDelete(dict *ht, const void *key) { unsigned int h; dictEntry *de, *prevde; if (ht->size == 0) return DICT_ERR; h = dictHashKey(ht, key) & ht->sizemask; de = ht->table[h]; prevde = NULL; while(de) { if (dictCompareHashKeys(ht,key,de->key)) { /* Unlink the element from the list */ if (prevde) prevde->next = de->next; else ht->table[h] = de->next; dictFreeEntryKey(ht,de); dictFreeEntryVal(ht,de); hi_free(de); ht->used--; return DICT_OK; } prevde = de; de = de->next; } return DICT_ERR; /* not found */ } /* Destroy an entire hash table */ static int _dictClear(dict *ht) { unsigned long i; /* Free all the elements */ for (i = 0; i < ht->size && ht->used > 0; i++) { dictEntry *he, *nextHe; if ((he = ht->table[i]) == NULL) continue; while(he) { nextHe = he->next; dictFreeEntryKey(ht, he); dictFreeEntryVal(ht, he); hi_free(he); ht->used--; he = nextHe; } } /* Free the table and the allocated cache structure */ hi_free(ht->table); /* Re-initialize the table */ _dictReset(ht); return DICT_OK; /* never fails */ } /* Clear & Release the hash table */ static void dictRelease(dict *ht) { _dictClear(ht); hi_free(ht); } static dictEntry *dictFind(dict *ht, const void *key) { dictEntry *he; unsigned int h; if (ht->size == 0) return NULL; h = dictHashKey(ht, key) & ht->sizemask; he = ht->table[h]; while(he) { if (dictCompareHashKeys(ht, key, he->key)) return he; he = he->next; } return NULL; } static void dictInitIterator(dictIterator *iter, dict *ht) { iter->ht = ht; iter->index = -1; iter->entry = NULL; iter->nextEntry = NULL; } static dictEntry *dictNext(dictIterator *iter) { while (1) { if (iter->entry == NULL) { iter->index++; if (iter->index >= (signed)iter->ht->size) break; iter->entry = iter->ht->table[iter->index]; } else { iter->entry = iter->nextEntry; } if (iter->entry) { /* We need to save the 'next' here, the iterator user * may delete the entry we are returning. */ iter->nextEntry = iter->entry->next; return iter->entry; } } return NULL; } /* ------------------------- private functions ------------------------------ */ /* Expand the hash table if needed */ static int _dictExpandIfNeeded(dict *ht) { /* If the hash table is empty expand it to the initial size, * if the table is "full" double its size. */ if (ht->size == 0) return dictExpand(ht, DICT_HT_INITIAL_SIZE); if (ht->used == ht->size) return dictExpand(ht, ht->size*2); return DICT_OK; } /* Our hash table capability is a power of two */ static unsigned long _dictNextPower(unsigned long size) { unsigned long i = DICT_HT_INITIAL_SIZE; if (size >= LONG_MAX) return LONG_MAX; while(1) { if (i >= size) return i; i *= 2; } } /* Returns the index of a free slot that can be populated with * an hash entry for the given 'key'. * If the key already exists, -1 is returned. */ static int _dictKeyIndex(dict *ht, const void *key) { unsigned int h; dictEntry *he; /* Expand the hashtable if needed */ if (_dictExpandIfNeeded(ht) == DICT_ERR) return -1; /* Compute the key hash value */ h = dictHashKey(ht, key) & ht->sizemask; /* Search if this slot does not already contain the given key */ he = ht->table[h]; while(he) { if (dictCompareHashKeys(ht, key, he->key)) return -1; he = he->next; } return h; } hiredict/dict.h000066400000000000000000000064611460333451400137570ustar00rootroot00000000000000/* Hash table implementation. * * This file implements in memory hash tables with insert/del/replace/find/ * get-random-element operations. Hash tables will auto resize if needed * tables of power of two in size are used, collisions are handled by * chaining. See the source code for more information... :) * * Copyright (c) 2006-2010, Salvatore Sanfilippo * All rights reserved. * * SPDX-FileCopyrightText: 2024 Hiredict Contributors * SPDX-FileCopyrightText: 2024 Salvatore Sanfilippo * * SPDX-License-Identifier: BSD-3-Clause * SPDX-License-Identifier: LGPL-3.0-or-later * */ #ifndef __DICT_H #define __DICT_H #define DICT_OK 0 #define DICT_ERR 1 /* Unused arguments generate annoying warnings... */ #define DICT_NOTUSED(V) ((void) V) typedef struct dictEntry { void *key; void *val; struct dictEntry *next; } dictEntry; typedef struct dictType { unsigned int (*hashFunction)(const void *key); void *(*keyDup)(void *privdata, const void *key); void *(*valDup)(void *privdata, const void *obj); int (*keyCompare)(void *privdata, const void *key1, const void *key2); void (*keyDestructor)(void *privdata, void *key); void (*valDestructor)(void *privdata, void *obj); } dictType; typedef struct dict { dictEntry **table; dictType *type; unsigned long size; unsigned long sizemask; unsigned long used; void *privdata; } dict; typedef struct dictIterator { dict *ht; int index; dictEntry *entry, *nextEntry; } dictIterator; /* This is the initial size of every hash table */ #define DICT_HT_INITIAL_SIZE 4 /* ------------------------------- Macros ------------------------------------*/ #define dictFreeEntryVal(ht, entry) \ if ((ht)->type->valDestructor) \ (ht)->type->valDestructor((ht)->privdata, (entry)->val) #define dictSetHashVal(ht, entry, _val_) do { \ if ((ht)->type->valDup) \ entry->val = (ht)->type->valDup((ht)->privdata, _val_); \ else \ entry->val = (_val_); \ } while(0) #define dictFreeEntryKey(ht, entry) \ if ((ht)->type->keyDestructor) \ (ht)->type->keyDestructor((ht)->privdata, (entry)->key) #define dictSetHashKey(ht, entry, _key_) do { \ if ((ht)->type->keyDup) \ entry->key = (ht)->type->keyDup((ht)->privdata, _key_); \ else \ entry->key = (_key_); \ } while(0) #define dictCompareHashKeys(ht, key1, key2) \ (((ht)->type->keyCompare) ? \ (ht)->type->keyCompare((ht)->privdata, key1, key2) : \ (key1) == (key2)) #define dictHashKey(ht, key) (ht)->type->hashFunction(key) #define dictGetEntryKey(he) ((he)->key) #define dictGetEntryVal(he) ((he)->val) #define dictSlots(ht) ((ht)->size) #define dictSize(ht) ((ht)->used) /* API */ static unsigned int dictGenHashFunction(const unsigned char *buf, int len); static dict *dictCreate(dictType *type, void *privDataPtr); static int dictExpand(dict *ht, unsigned long size); static int dictAdd(dict *ht, void *key, void *val); static int dictReplace(dict *ht, void *key, void *val); static int dictDelete(dict *ht, const void *key); static void dictRelease(dict *ht); static dictEntry * dictFind(dict *ht, const void *key); static void dictInitIterator(dictIterator *iter, dict *ht); static dictEntry *dictNext(dictIterator *iter); #endif /* __DICT_H */ hiredict/examples/000077500000000000000000000000001460333451400144725ustar00rootroot00000000000000hiredict/examples/CMakeLists.txt000066400000000000000000000035471460333451400172430ustar00rootroot00000000000000# SPDX-FileCopyrightText: 2024 Hiredict Contributors # SPDX-FileCopyrightText: 2024 Salvatore Sanfilippo # # SPDX-License-Identifier: BSD-3-Clause # SPDX-License-Identifier: LGPL-3.0-or-later INCLUDE(FindPkgConfig) # Check for GLib PKG_CHECK_MODULES(GLIB2 glib-2.0) if (GLIB2_FOUND) INCLUDE_DIRECTORIES(${GLIB2_INCLUDE_DIRS}) LINK_DIRECTORIES(${GLIB2_LIBRARY_DIRS}) ADD_EXECUTABLE(example-glib example-glib.c) TARGET_LINK_LIBRARIES(example-glib hiredict ${GLIB2_LIBRARIES}) ENDIF(GLIB2_FOUND) FIND_PATH(LIBEV ev.h HINTS /usr/local /usr/opt/local ENV LIBEV_INCLUDE_DIR) if (LIBEV) # Just compile and link with libev ADD_EXECUTABLE(example-libev example-libev.c) TARGET_LINK_LIBRARIES(example-libev hiredict ev) ENDIF() FIND_PATH(LIBEVENT event.h) if (LIBEVENT) ADD_EXECUTABLE(example-libevent example-libevent.c) TARGET_LINK_LIBRARIES(example-libevent hiredict event) ENDIF() FIND_PATH(LIBHV hv/hv.h) IF (LIBHV) ADD_EXECUTABLE(example-libhv example-libhv.c) TARGET_LINK_LIBRARIES(example-libhv hiredict hv) ENDIF() FIND_PATH(LIBUV uv.h) IF (LIBUV) ADD_EXECUTABLE(example-libuv example-libuv.c) TARGET_LINK_LIBRARIES(example-libuv hiredict uv) ENDIF() FIND_PATH(LIBSDEVENT systemd/sd-event.h) IF (LIBSDEVENT) ADD_EXECUTABLE(example-libsdevent example-libsdevent.c) TARGET_LINK_LIBRARIES(example-libsdevent hiredict systemd) ENDIF() IF (APPLE) FIND_LIBRARY(CF CoreFoundation) ADD_EXECUTABLE(example-macosx example-macosx.c) TARGET_LINK_LIBRARIES(example-macosx hiredict ${CF}) ENDIF() IF (ENABLE_SSL) ADD_EXECUTABLE(example-ssl example-ssl.c) TARGET_LINK_LIBRARIES(example-ssl hiredict hiredict_ssl) ENDIF() ADD_EXECUTABLE(example example.c) TARGET_LINK_LIBRARIES(example hiredict) ADD_EXECUTABLE(example-push example-push.c) TARGET_LINK_LIBRARIES(example-push hiredict) hiredict/examples/example-ae.c000066400000000000000000000034371460333451400166630ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2024 Hiredict Contributors // SPDX-FileCopyrightText: 2024 Salvatore Sanfilippo // // SPDX-License-Identifier: BSD-3-Clause // SPDX-License-Identifier: LGPL-3.0-or-later #include #include #include #include #include #include #include /* Put event loop in the global scope, so it can be explicitly stopped */ static aeEventLoop *loop; void getCallback(redictAsyncContext *c, void *r, void *privdata) { redictReply *reply = r; if (reply == NULL) return; printf("argv[%s]: %s\n", (char*)privdata, reply->str); /* Disconnect after receiving the reply to GET */ redictAsyncDisconnect(c); } void connectCallback(const redictAsyncContext *c, int status) { if (status != REDICT_OK) { printf("Error: %s\n", c->errstr); aeStop(loop); return; } printf("Connected...\n"); } void disconnectCallback(const redictAsyncContext *c, int status) { if (status != REDICT_OK) { printf("Error: %s\n", c->errstr); aeStop(loop); return; } printf("Disconnected...\n"); aeStop(loop); } int main (int argc, char **argv) { signal(SIGPIPE, SIG_IGN); redictAsyncContext *c = redictAsyncConnect("127.0.0.1", 6379); if (c->err) { /* Let *c leak for now... */ printf("Error: %s\n", c->errstr); return 1; } loop = aeCreateEventLoop(64); redictAeAttach(loop, c); redictAsyncSetConnectCallback(c,connectCallback); redictAsyncSetDisconnectCallback(c,disconnectCallback); redictAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1])); redictAsyncCommand(c, getCallback, (char*)"end-1", "GET key"); aeMain(loop); return 0; } hiredict/examples/example-glib.c000066400000000000000000000035201460333451400172040ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2024 Hiredict Contributors // SPDX-FileCopyrightText: 2024 Salvatore Sanfilippo // // SPDX-License-Identifier: BSD-3-Clause // SPDX-License-Identifier: LGPL-3.0-or-later #include #include #include #include static GMainLoop *mainloop; static void connect_cb (const redictAsyncContext *ac G_GNUC_UNUSED, int status) { if (status != REDICT_OK) { g_printerr("Failed to connect: %s\n", ac->errstr); g_main_loop_quit(mainloop); } else { g_printerr("Connected...\n"); } } static void disconnect_cb (const redictAsyncContext *ac G_GNUC_UNUSED, int status) { if (status != REDICT_OK) { g_error("Failed to disconnect: %s", ac->errstr); } else { g_printerr("Disconnected...\n"); g_main_loop_quit(mainloop); } } static void command_cb(redictAsyncContext *ac, gpointer r, gpointer user_data G_GNUC_UNUSED) { redictReply *reply = r; if (reply) { g_print("REPLY: %s\n", reply->str); } redictAsyncDisconnect(ac); } gint main (gint argc G_GNUC_UNUSED, gchar *argv[] G_GNUC_UNUSED) { redictAsyncContext *ac; GMainContext *context = NULL; GSource *source; ac = redictAsyncConnect("127.0.0.1", 6379); if (ac->err) { g_printerr("%s\n", ac->errstr); exit(EXIT_FAILURE); } source = redict_source_new(ac); mainloop = g_main_loop_new(context, FALSE); g_source_attach(source, context); redictAsyncSetConnectCallback(ac, connect_cb); redictAsyncSetDisconnectCallback(ac, disconnect_cb); redictAsyncCommand(ac, command_cb, NULL, "SET key 1234"); redictAsyncCommand(ac, command_cb, NULL, "GET key"); g_main_loop_run(mainloop); return EXIT_SUCCESS; } hiredict/examples/example-ivykis.c000066400000000000000000000032201460333451400176020ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2024 Hiredict Contributors // SPDX-FileCopyrightText: 2024 Salvatore Sanfilippo // // SPDX-License-Identifier: BSD-3-Clause // SPDX-License-Identifier: LGPL-3.0-or-later #include #include #include #include #include #include #include void getCallback(redictAsyncContext *c, void *r, void *privdata) { redictReply *reply = r; if (reply == NULL) return; printf("argv[%s]: %s\n", (char*)privdata, reply->str); /* Disconnect after receiving the reply to GET */ redictAsyncDisconnect(c); } void connectCallback(const redictAsyncContext *c, int status) { if (status != REDICT_OK) { printf("Error: %s\n", c->errstr); return; } printf("Connected...\n"); } void disconnectCallback(const redictAsyncContext *c, int status) { if (status != REDICT_OK) { printf("Error: %s\n", c->errstr); return; } printf("Disconnected...\n"); } int main (int argc, char **argv) { #ifndef _WIN32 signal(SIGPIPE, SIG_IGN); #endif iv_init(); redictAsyncContext *c = redictAsyncConnect("127.0.0.1", 6379); if (c->err) { /* Let *c leak for now... */ printf("Error: %s\n", c->errstr); return 1; } redictIvykisAttach(c); redictAsyncSetConnectCallback(c,connectCallback); redictAsyncSetDisconnectCallback(c,disconnectCallback); redictAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1])); redictAsyncCommand(c, getCallback, (char*)"end-1", "GET key"); iv_main(); iv_deinit(); return 0; } hiredict/examples/example-libev.c000066400000000000000000000032031460333451400173660ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2024 Hiredict Contributors // SPDX-FileCopyrightText: 2024 Salvatore Sanfilippo // // SPDX-License-Identifier: BSD-3-Clause // SPDX-License-Identifier: LGPL-3.0-or-later #include #include #include #include #include #include #include void getCallback(redictAsyncContext *c, void *r, void *privdata) { redictReply *reply = r; if (reply == NULL) return; printf("argv[%s]: %s\n", (char*)privdata, reply->str); /* Disconnect after receiving the reply to GET */ redictAsyncDisconnect(c); } void connectCallback(const redictAsyncContext *c, int status) { if (status != REDICT_OK) { printf("Error: %s\n", c->errstr); return; } printf("Connected...\n"); } void disconnectCallback(const redictAsyncContext *c, int status) { if (status != REDICT_OK) { printf("Error: %s\n", c->errstr); return; } printf("Disconnected...\n"); } int main (int argc, char **argv) { #ifndef _WIN32 signal(SIGPIPE, SIG_IGN); #endif redictAsyncContext *c = redictAsyncConnect("127.0.0.1", 6379); if (c->err) { /* Let *c leak for now... */ printf("Error: %s\n", c->errstr); return 1; } redictLibevAttach(EV_DEFAULT_ c); redictAsyncSetConnectCallback(c,connectCallback); redictAsyncSetDisconnectCallback(c,disconnectCallback); redictAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1])); redictAsyncCommand(c, getCallback, (char*)"end-1", "GET key"); ev_loop(EV_DEFAULT_ 0); return 0; } hiredict/examples/example-libevent-ssl.c000066400000000000000000000050411460333451400206760ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2024 Hiredict Contributors // SPDX-FileCopyrightText: 2024 Salvatore Sanfilippo // // SPDX-License-Identifier: BSD-3-Clause // SPDX-License-Identifier: LGPL-3.0-or-later #include #include #include #include #include #include #include #include void getCallback(redictAsyncContext *c, void *r, void *privdata) { redictReply *reply = r; if (reply == NULL) return; printf("argv[%s]: %s\n", (char*)privdata, reply->str); /* Disconnect after receiving the reply to GET */ redictAsyncDisconnect(c); } void connectCallback(const redictAsyncContext *c, int status) { if (status != REDICT_OK) { printf("Error: %s\n", c->errstr); return; } printf("Connected...\n"); } void disconnectCallback(const redictAsyncContext *c, int status) { if (status != REDICT_OK) { printf("Error: %s\n", c->errstr); return; } printf("Disconnected...\n"); } int main (int argc, char **argv) { #ifndef _WIN32 signal(SIGPIPE, SIG_IGN); #endif struct event_base *base = event_base_new(); if (argc < 5) { fprintf(stderr, "Usage: %s [ca]\n", argv[0]); exit(1); } const char *value = argv[1]; size_t nvalue = strlen(value); const char *hostname = argv[2]; int port = atoi(argv[3]); const char *cert = argv[4]; const char *certKey = argv[5]; const char *caCert = argc > 5 ? argv[6] : NULL; redictSSLContext *ssl; redictSSLContextError ssl_error = REDICT_SSL_CTX_NONE; redictInitOpenSSL(); ssl = redictCreateSSLContext(caCert, NULL, cert, certKey, NULL, &ssl_error); if (!ssl) { printf("Error: %s\n", redictSSLContextGetError(ssl_error)); return 1; } redictAsyncContext *c = redictAsyncConnect(hostname, port); if (c->err) { /* Let *c leak for now... */ printf("Error: %s\n", c->errstr); return 1; } if (redictInitiateSSLWithContext(&c->c, ssl) != REDICT_OK) { printf("SSL Error!\n"); exit(1); } redictLibeventAttach(c,base); redictAsyncSetConnectCallback(c,connectCallback); redictAsyncSetDisconnectCallback(c,disconnectCallback); redictAsyncCommand(c, NULL, NULL, "SET key %b", value, nvalue); redictAsyncCommand(c, getCallback, (char*)"end-1", "GET key"); event_base_dispatch(base); redictFreeSSLContext(ssl); return 0; } hiredict/examples/example-libevent.c000066400000000000000000000037101460333451400201000ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2024 Hiredict Contributors // SPDX-FileCopyrightText: 2024 Salvatore Sanfilippo // // SPDX-License-Identifier: BSD-3-Clause // SPDX-License-Identifier: LGPL-3.0-or-later #include #include #include #include #include #include #include void getCallback(redictAsyncContext *c, void *r, void *privdata) { redictReply *reply = r; if (reply == NULL) { if (c->errstr) { printf("errstr: %s\n", c->errstr); } return; } printf("argv[%s]: %s\n", (char*)privdata, reply->str); /* Disconnect after receiving the reply to GET */ redictAsyncDisconnect(c); } void connectCallback(const redictAsyncContext *c, int status) { if (status != REDICT_OK) { printf("Error: %s\n", c->errstr); return; } printf("Connected...\n"); } void disconnectCallback(const redictAsyncContext *c, int status) { if (status != REDICT_OK) { printf("Error: %s\n", c->errstr); return; } printf("Disconnected...\n"); } int main (int argc, char **argv) { #ifndef _WIN32 signal(SIGPIPE, SIG_IGN); #endif struct event_base *base = event_base_new(); redictOptions options = {0}; REDICT_OPTIONS_SET_TCP(&options, "127.0.0.1", 6379); struct timeval tv = {0}; tv.tv_sec = 1; options.connect_timeout = &tv; redictAsyncContext *c = redictAsyncConnectWithOptions(&options); if (c->err) { /* Let *c leak for now... */ printf("Error: %s\n", c->errstr); return 1; } redictLibeventAttach(c,base); redictAsyncSetConnectCallback(c,connectCallback); redictAsyncSetDisconnectCallback(c,disconnectCallback); redictAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1])); redictAsyncCommand(c, getCallback, (char*)"end-1", "GET key"); event_base_dispatch(base); return 0; } hiredict/examples/example-libhv.c000066400000000000000000000042051460333451400173740ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2024 Hiredict Contributors // SPDX-FileCopyrightText: 2024 Salvatore Sanfilippo // // SPDX-License-Identifier: BSD-3-Clause // SPDX-License-Identifier: LGPL-3.0-or-later #include #include #include #include #include #include #include void getCallback(redictAsyncContext *c, void *r, void *privdata) { redictReply *reply = r; if (reply == NULL) return; printf("argv[%s]: %s\n", (char*)privdata, reply->str); /* Disconnect after receiving the reply to GET */ redictAsyncDisconnect(c); } void debugCallback(redictAsyncContext *c, void *r, void *privdata) { (void)privdata; redictReply *reply = r; if (reply == NULL) { printf("`DEBUG SLEEP` error: %s\n", c->errstr ? c->errstr : "unknown error"); return; } redictAsyncDisconnect(c); } void connectCallback(const redictAsyncContext *c, int status) { if (status != REDICT_OK) { printf("Error: %s\n", c->errstr); return; } printf("Connected...\n"); } void disconnectCallback(const redictAsyncContext *c, int status) { if (status != REDICT_OK) { printf("Error: %s\n", c->errstr); return; } printf("Disconnected...\n"); } int main (int argc, char **argv) { #ifndef _WIN32 signal(SIGPIPE, SIG_IGN); #endif redictAsyncContext *c = redictAsyncConnect("127.0.0.1", 6379); if (c->err) { /* Let *c leak for now... */ printf("Error: %s\n", c->errstr); return 1; } hloop_t* loop = hloop_new(HLOOP_FLAG_QUIT_WHEN_NO_ACTIVE_EVENTS); redictLibhvAttach(c, loop); redictAsyncSetTimeout(c, (struct timeval){.tv_sec = 0, .tv_usec = 500000}); redictAsyncSetConnectCallback(c,connectCallback); redictAsyncSetDisconnectCallback(c,disconnectCallback); redictAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1])); redictAsyncCommand(c, getCallback, (char*)"end-1", "GET key"); redictAsyncCommand(c, debugCallback, NULL, "DEBUG SLEEP %d", 1); hloop_run(loop); hloop_free(&loop); return 0; } hiredict/examples/example-libsdevent.c000066400000000000000000000060071460333451400204310ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2024 Hiredict Contributors // SPDX-FileCopyrightText: 2024 Salvatore Sanfilippo // // SPDX-License-Identifier: BSD-3-Clause // SPDX-License-Identifier: LGPL-3.0-or-later #include #include #include #include #include #include #include void debugCallback(redictAsyncContext *c, void *r, void *privdata) { (void)privdata; redictReply *reply = r; if (reply == NULL) { /* The DEBUG SLEEP command will almost always fail, because we have set a 1 second timeout */ printf("`DEBUG SLEEP` error: %s\n", c->errstr ? c->errstr : "unknown error"); return; } /* Disconnect after receiving the reply of DEBUG SLEEP (which will not)*/ redictAsyncDisconnect(c); } void getCallback(redictAsyncContext *c, void *r, void *privdata) { redictReply *reply = r; if (reply == NULL) { printf("`GET key` error: %s\n", c->errstr ? c->errstr : "unknown error"); return; } printf("`GET key` result: argv[%s]: %s\n", (char*)privdata, reply->str); /* start another request that demonstrate timeout */ redictAsyncCommand(c, debugCallback, NULL, "DEBUG SLEEP %f", 1.5); } void connectCallback(const redictAsyncContext *c, int status) { if (status != REDICT_OK) { printf("connect error: %s\n", c->errstr); return; } printf("Connected...\n"); } void disconnectCallback(const redictAsyncContext *c, int status) { if (status != REDICT_OK) { printf("disconnect because of error: %s\n", c->errstr); return; } printf("Disconnected...\n"); } int main (int argc, char **argv) { signal(SIGPIPE, SIG_IGN); struct sd_event *event; sd_event_default(&event); redictAsyncContext *c = redictAsyncConnect("127.0.0.1", 6379); if (c->err) { printf("Error: %s\n", c->errstr); redictAsyncFree(c); return 1; } redictLibsdeventAttach(c,event); redictAsyncSetConnectCallback(c,connectCallback); redictAsyncSetDisconnectCallback(c,disconnectCallback); redictAsyncSetTimeout(c, (struct timeval){ .tv_sec = 1, .tv_usec = 0}); /* In this demo, we first `set key`, then `get key` to demonstrate the basic usage of libsdevent adapter. Then in `getCallback`, we start a `debug sleep` command to create 1.5 second long request. Because we have set a 1 second timeout to the connection, the command will always fail with a timeout error, which is shown in the `debugCallback`. */ redictAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1])); redictAsyncCommand(c, getCallback, (char*)"end-1", "GET key"); /* sd-event does not quit when there are no handlers registered. Manually exit after 1.5 seconds */ sd_event_source *s; sd_event_add_time_relative(event, &s, CLOCK_MONOTONIC, 1500000, 1, NULL, NULL); sd_event_loop(event); sd_event_source_disable_unref(s); sd_event_unref(event); return 0; } hiredict/examples/example-libuv.c000066400000000000000000000054001460333451400174070ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2024 Hiredict Contributors // SPDX-FileCopyrightText: 2024 Salvatore Sanfilippo // // SPDX-License-Identifier: BSD-3-Clause // SPDX-License-Identifier: LGPL-3.0-or-later #include #include #include #include #include #include #include void debugCallback(redictAsyncContext *c, void *r, void *privdata) { (void)privdata; //unused redictReply *reply = r; if (reply == NULL) { /* The DEBUG SLEEP command will almost always fail, because we have set a 1 second timeout */ printf("`DEBUG SLEEP` error: %s\n", c->errstr ? c->errstr : "unknown error"); return; } /* Disconnect after receiving the reply of DEBUG SLEEP (which will not)*/ redictAsyncDisconnect(c); } void getCallback(redictAsyncContext *c, void *r, void *privdata) { redictReply *reply = r; if (reply == NULL) { printf("`GET key` error: %s\n", c->errstr ? c->errstr : "unknown error"); return; } printf("`GET key` result: argv[%s]: %s\n", (char*)privdata, reply->str); /* start another request that demonstrate timeout */ redictAsyncCommand(c, debugCallback, NULL, "DEBUG SLEEP %f", 1.5); } void connectCallback(const redictAsyncContext *c, int status) { if (status != REDICT_OK) { printf("connect error: %s\n", c->errstr); return; } printf("Connected...\n"); } void disconnectCallback(const redictAsyncContext *c, int status) { if (status != REDICT_OK) { printf("disconnect because of error: %s\n", c->errstr); return; } printf("Disconnected...\n"); } int main (int argc, char **argv) { #ifndef _WIN32 signal(SIGPIPE, SIG_IGN); #endif uv_loop_t* loop = uv_default_loop(); redictAsyncContext *c = redictAsyncConnect("127.0.0.1", 6379); if (c->err) { /* Let *c leak for now... */ printf("Error: %s\n", c->errstr); return 1; } redictLibuvAttach(c,loop); redictAsyncSetConnectCallback(c,connectCallback); redictAsyncSetDisconnectCallback(c,disconnectCallback); redictAsyncSetTimeout(c, (struct timeval){ .tv_sec = 1, .tv_usec = 0}); /* In this demo, we first `set key`, then `get key` to demonstrate the basic usage of libuv adapter. Then in `getCallback`, we start a `debug sleep` command to create 1.5 second long request. Because we have set a 1 second timeout to the connection, the command will always fail with a timeout error, which is shown in the `debugCallback`. */ redictAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1])); redictAsyncCommand(c, getCallback, (char*)"end-1", "GET key"); uv_run(loop, UV_RUN_DEFAULT); return 0; } hiredict/examples/example-macosx.c000066400000000000000000000033601460333451400175630ustar00rootroot00000000000000// Copyright (c) 2015 Dmitry Bakhvalov // // SPDX-FileCopyrightText: 2024 Hiredict Contributors // SPDX-FileCopyrightText: 2024 Dmitry Bakhvalov // // SPDX-License-Identifier: BSD-3-Clause // SPDX-License-Identifier: LGPL-3.0-or-later #include #include #include #include void getCallback(redictAsyncContext *c, void *r, void *privdata) { redictReply *reply = r; if (reply == NULL) return; printf("argv[%s]: %s\n", (char*)privdata, reply->str); /* Disconnect after receiving the reply to GET */ redictAsyncDisconnect(c); } void connectCallback(const redictAsyncContext *c, int status) { if (status != REDICT_OK) { printf("Error: %s\n", c->errstr); return; } printf("Connected...\n"); } void disconnectCallback(const redictAsyncContext *c, int status) { if (status != REDICT_OK) { printf("Error: %s\n", c->errstr); return; } CFRunLoopStop(CFRunLoopGetCurrent()); printf("Disconnected...\n"); } int main (int argc, char **argv) { signal(SIGPIPE, SIG_IGN); CFRunLoopRef loop = CFRunLoopGetCurrent(); if( !loop ) { printf("Error: Cannot get current run loop\n"); return 1; } redictAsyncContext *c = redictAsyncConnect("127.0.0.1", 6379); if (c->err) { /* Let *c leak for now... */ printf("Error: %s\n", c->errstr); return 1; } redictMacOSAttach(c, loop); redictAsyncSetConnectCallback(c,connectCallback); redictAsyncSetDisconnectCallback(c,disconnectCallback); redictAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1])); redictAsyncCommand(c, getCallback, (char*)"end-1", "GET key"); CFRunLoopRun(); return 0; } hiredict/examples/example-poll.c000066400000000000000000000034211460333451400172350ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2024 Hiredict Contributors // SPDX-FileCopyrightText: 2024 Salvatore Sanfilippo // // SPDX-License-Identifier: BSD-3-Clause // SPDX-License-Identifier: LGPL-3.0-or-later #include #include #include #include #include #include #include /* Put in the global scope, so that loop can be explicitly stopped */ static int exit_loop = 0; void getCallback(redictAsyncContext *c, void *r, void *privdata) { redictReply *reply = r; if (reply == NULL) return; printf("argv[%s]: %s\n", (char*)privdata, reply->str); /* Disconnect after receiving the reply to GET */ redictAsyncDisconnect(c); } void connectCallback(const redictAsyncContext *c, int status) { if (status != REDICT_OK) { printf("Error: %s\n", c->errstr); exit_loop = 1; return; } printf("Connected...\n"); } void disconnectCallback(const redictAsyncContext *c, int status) { exit_loop = 1; if (status != REDICT_OK) { printf("Error: %s\n", c->errstr); return; } printf("Disconnected...\n"); } int main (int argc, char **argv) { signal(SIGPIPE, SIG_IGN); redictAsyncContext *c = redictAsyncConnect("127.0.0.1", 6379); if (c->err) { /* Let *c leak for now... */ printf("Error: %s\n", c->errstr); return 1; } redictPollAttach(c); redictAsyncSetConnectCallback(c,connectCallback); redictAsyncSetDisconnectCallback(c,disconnectCallback); redictAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1])); redictAsyncCommand(c, getCallback, (char*)"end-1", "GET key"); while (!exit_loop) { redictPollTick(c, 0.1); } return 0; } hiredict/examples/example-push.c000066400000000000000000000112051460333451400172450ustar00rootroot00000000000000// Copyright (c) 2020, Michael Grunder // // SPDX-FileCopyrightText: 2024 Hiredict Contributors // SPDX-FileCopyrightText: 2024 Michael Grunder // // SPDX-License-Identifier: BSD-3-Clause // SPDX-License-Identifier: LGPL-3.0-or-later #include #include #include #include #define KEY_COUNT 5 #define panicAbort(fmt, ...) \ do { \ fprintf(stderr, "%s:%d:%s(): " fmt, __FILE__, __LINE__, __func__, __VA_ARGS__); \ exit(-1); \ } while (0) static void assertReplyAndFree(redictContext *context, redictReply *reply, int type) { if (reply == NULL) panicAbort("NULL reply from server (error: %s)", context->errstr); if (reply->type != type) { if (reply->type == REDICT_REPLY_ERROR) fprintf(stderr, "Redict Error: %s\n", reply->str); panicAbort("Expected reply type %d but got type %d", type, reply->type); } freeReplyObject(reply); } /* Switch to the RESP3 protocol and enable client tracking */ static void enableClientTracking(redictContext *c) { redictReply *reply = redictCommand(c, "HELLO 3"); if (reply == NULL || c->err) { panicAbort("NULL reply or server error (error: %s)", c->errstr); } if (reply->type != REDICT_REPLY_MAP) { fprintf(stderr, "Error: Can't send HELLO 3 command. Are you sure you're "); fprintf(stderr, "connected to redict-server >= 6.0.0?\nRedict error: %s\n", reply->type == REDICT_REPLY_ERROR ? reply->str : "(unknown)"); exit(-1); } freeReplyObject(reply); /* Enable client tracking */ reply = redictCommand(c, "CLIENT TRACKING ON"); assertReplyAndFree(c, reply, REDICT_REPLY_STATUS); } void pushReplyHandler(void *privdata, void *r) { redictReply *reply = r; int *invalidations = privdata; /* Sanity check on the invalidation reply */ if (reply->type != REDICT_REPLY_PUSH || reply->elements != 2 || reply->element[1]->type != REDICT_REPLY_ARRAY || reply->element[1]->element[0]->type != REDICT_REPLY_STRING) { panicAbort("%s", "Can't parse PUSH message!"); } /* Increment our invalidation count */ *invalidations += 1; printf("pushReplyHandler(): INVALIDATE '%s' (invalidation count: %d)\n", reply->element[1]->element[0]->str, *invalidations); freeReplyObject(reply); } /* We aren't actually freeing anything here, but it is included to show that we can * have hiredict call our data destructor when freeing the context */ void privdata_dtor(void *privdata) { unsigned int *icount = privdata; printf("privdata_dtor(): In context privdata dtor (invalidations: %u)\n", *icount); } int main(int argc, char **argv) { unsigned int j, invalidations = 0; redictContext *c; redictReply *reply; const char *hostname = (argc > 1) ? argv[1] : "127.0.0.1"; int port = (argc > 2) ? atoi(argv[2]) : 6379; redictOptions o = {0}; REDICT_OPTIONS_SET_TCP(&o, hostname, port); /* Set our context privdata to the address of our invalidation counter. Each * time our PUSH handler is called, hiredict will pass the privdata for context. * * This could also be done after we create the context like so: * * c->privdata = &invalidations; * c->free_privdata = privdata_dtor; */ REDICT_OPTIONS_SET_PRIVDATA(&o, &invalidations, privdata_dtor); /* Set our custom PUSH message handler */ o.push_cb = pushReplyHandler; c = redictConnectWithOptions(&o); if (c == NULL || c->err) panicAbort("Connection error: %s", c ? c->errstr : "OOM"); /* Enable RESP3 and turn on client tracking */ enableClientTracking(c); /* Set some keys and then read them back. Once we do that, Redict will deliver * invalidation push messages whenever the key is modified */ for (j = 0; j < KEY_COUNT; j++) { reply = redictCommand(c, "SET key:%d initial:%d", j, j); assertReplyAndFree(c, reply, REDICT_REPLY_STATUS); reply = redictCommand(c, "GET key:%d", j); assertReplyAndFree(c, reply, REDICT_REPLY_STRING); } /* Trigger invalidation messages by updating keys we just read */ for (j = 0; j < KEY_COUNT; j++) { printf(" main(): SET key:%d update:%d\n", j, j); reply = redictCommand(c, "SET key:%d update:%d", j, j); assertReplyAndFree(c, reply, REDICT_REPLY_STATUS); printf(" main(): SET REPLY OK\n"); } printf("\nTotal detected invalidations: %d, expected: %d\n", invalidations, KEY_COUNT); /* PING server */ redictFree(c); } hiredict/examples/example-qt.cpp000066400000000000000000000023771460333451400172640ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2024 Hiredict Contributors // SPDX-FileCopyrightText: 2024 Salvatore Sanfilippo // // SPDX-License-Identifier: BSD-3-Clause // SPDX-License-Identifier: LGPL-3.0-or-later #include using namespace std; #include #include #include "example-qt.h" void getCallback(redictAsyncContext *, void * r, void * privdata) { redictReply * reply = static_cast(r); ExampleQt * ex = static_cast(privdata); if (reply == nullptr || ex == nullptr) return; cout << "key: " << reply->str << endl; ex->finish(); } void ExampleQt::run() { m_ctx = redictAsyncConnect("localhost", 6379); if (m_ctx->err) { cerr << "Error: " << m_ctx->errstr << endl; redictAsyncFree(m_ctx); emit finished(); } m_adapter.setContext(m_ctx); redictAsyncCommand(m_ctx, NULL, NULL, "SET key %s", m_value); redictAsyncCommand(m_ctx, getCallback, this, "GET key"); } int main (int argc, char **argv) { QCoreApplication app(argc, argv); ExampleQt example(argv[argc-1]); QObject::connect(&example, SIGNAL(finished()), &app, SLOT(quit())); QTimer::singleShot(0, &example, SLOT(run())); return app.exec(); } hiredict/examples/example-qt.h000066400000000000000000000015231460333451400167210ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2024 Hiredict Contributors // SPDX-FileCopyrightText: 2024 Salvatore Sanfilippo // // SPDX-License-Identifier: BSD-3-Clause // SPDX-License-Identifier: LGPL-3.0-or-later #ifndef __HIREDICT_EXAMPLE_QT_H #define __HIREDICT_EXAMPLE_QT_H #include class ExampleQt : public QObject { Q_OBJECT public: ExampleQt(const char * value, QObject * parent = 0) : QObject(parent), m_value(value) {} signals: void finished(); public slots: void run(); private: void finish() { emit finished(); } private: const char * m_value; redictAsyncContext * m_ctx; RedictQtAdapter m_adapter; friend void getCallback(redictAsyncContext *, void *, void *); }; #endif /* !__HIREDICT_EXAMPLE_QT_H */ hiredict/examples/example-redictmoduleapi.c000066400000000000000000000066131460333451400214470ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2024 Hiredict Contributors // SPDX-FileCopyrightText: 2024 Salvatore Sanfilippo // // SPDX-License-Identifier: BSD-3-Clause // SPDX-License-Identifier: LGPL-3.0-or-later #include #include #include #include #include #include #include void debugCallback(redictAsyncContext *c, void *r, void *privdata) { (void)privdata; //unused redictReply *reply = r; if (reply == NULL) { /* The DEBUG SLEEP command will almost always fail, because we have set a 1 second timeout */ printf("`DEBUG SLEEP` error: %s\n", c->errstr ? c->errstr : "unknown error"); return; } /* Disconnect after receiving the reply of DEBUG SLEEP (which will not)*/ redictAsyncDisconnect(c); } void getCallback(redictAsyncContext *c, void *r, void *privdata) { redictReply *reply = r; if (reply == NULL) { if (c->errstr) { printf("errstr: %s\n", c->errstr); } return; } printf("argv[%s]: %s\n", (char*)privdata, reply->str); /* start another request that demonstrate timeout */ redictAsyncCommand(c, debugCallback, NULL, "DEBUG SLEEP %f", 1.5); } void connectCallback(const redictAsyncContext *c, int status) { if (status != REDICT_OK) { printf("Error: %s\n", c->errstr); return; } printf("Connected...\n"); } void disconnectCallback(const redictAsyncContext *c, int status) { if (status != REDICT_OK) { printf("Error: %s\n", c->errstr); return; } printf("Disconnected...\n"); } /* * 1- Compile this file as a shared library. Directory of "redictmodule.h" must * be in the include path. * gcc -fPIC -shared -I../../redict/src/ -I.. example-redictmoduleapi.c -o example-redictmoduleapi.so * * 2- Load module: * redict-server --loadmodule ./example-redictmoduleapi.so value */ int RedictModule_OnLoad(RedictModuleCtx *ctx, RedictModuleString **argv, int argc) { int ret = RedictModule_Init(ctx, "example-redictmoduleapi", 1, REDICTMODULE_APIVER_1); if (ret != REDICTMODULE_OK) { printf("error module init \n"); return REDICTMODULE_ERR; } if (redictModuleCompatibilityCheck() != REDICT_OK) { return REDICTMODULE_ERR; } redictAsyncContext *c = redictAsyncConnect("127.0.0.1", 6379); if (c->err) { /* Let *c leak for now... */ printf("Error: %s\n", c->errstr); return 1; } size_t len; const char *val = RedictModule_StringPtrLen(argv[argc-1], &len); RedictModuleCtx *module_ctx = RedictModule_GetDetachedThreadSafeContext(ctx); redictModuleAttach(c, module_ctx); redictAsyncSetConnectCallback(c,connectCallback); redictAsyncSetDisconnectCallback(c,disconnectCallback); redictAsyncSetTimeout(c, (struct timeval){ .tv_sec = 1, .tv_usec = 0}); /* In this demo, we first `set key`, then `get key` to demonstrate the basic usage of the adapter. Then in `getCallback`, we start a `debug sleep` command to create 1.5 second long request. Because we have set a 1 second timeout to the connection, the command will always fail with a timeout error, which is shown in the `debugCallback`. */ redictAsyncCommand(c, NULL, NULL, "SET key %b", val, len); redictAsyncCommand(c, getCallback, (char*)"end-1", "GET key"); return 0; } hiredict/examples/example-ssl.c000066400000000000000000000065741460333451400171040ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2024 Hiredict Contributors // SPDX-FileCopyrightText: 2024 Salvatore Sanfilippo // // SPDX-License-Identifier: BSD-3-Clause // SPDX-License-Identifier: LGPL-3.0-or-later #include #include #include #include #include #ifdef _MSC_VER #include /* For struct timeval */ #endif int main(int argc, char **argv) { unsigned int j; redictSSLContext *ssl; redictSSLContextError ssl_error = REDICT_SSL_CTX_NONE; redictContext *c; redictReply *reply; if (argc < 4) { printf("Usage: %s [ca]\n", argv[0]); exit(1); } const char *hostname = (argc > 1) ? argv[1] : "127.0.0.1"; int port = atoi(argv[2]); const char *cert = argv[3]; const char *key = argv[4]; const char *ca = argc > 4 ? argv[5] : NULL; redictInitOpenSSL(); ssl = redictCreateSSLContext(ca, NULL, cert, key, NULL, &ssl_error); if (!ssl || ssl_error != REDICT_SSL_CTX_NONE) { printf("SSL Context error: %s\n", redictSSLContextGetError(ssl_error)); exit(1); } struct timeval tv = { 1, 500000 }; // 1.5 seconds redictOptions options = {0}; REDICT_OPTIONS_SET_TCP(&options, hostname, port); options.connect_timeout = &tv; c = redictConnectWithOptions(&options); if (c == NULL || c->err) { if (c) { printf("Connection error: %s\n", c->errstr); redictFree(c); } else { printf("Connection error: can't allocate redict context\n"); } exit(1); } if (redictInitiateSSLWithContext(c, ssl) != REDICT_OK) { printf("Couldn't initialize SSL!\n"); printf("Error: %s\n", c->errstr); redictFree(c); exit(1); } /* PING server */ reply = redictCommand(c,"PING"); printf("PING: %s\n", reply->str); freeReplyObject(reply); /* Set a key */ reply = redictCommand(c,"SET %s %s", "foo", "hello world"); printf("SET: %s\n", reply->str); freeReplyObject(reply); /* Set a key using binary safe API */ reply = redictCommand(c,"SET %b %b", "bar", (size_t) 3, "hello", (size_t) 5); printf("SET (binary API): %s\n", reply->str); freeReplyObject(reply); /* Try a GET and two INCR */ reply = redictCommand(c,"GET foo"); printf("GET foo: %s\n", reply->str); freeReplyObject(reply); reply = redictCommand(c,"INCR counter"); printf("INCR counter: %lld\n", reply->integer); freeReplyObject(reply); /* again ... */ reply = redictCommand(c,"INCR counter"); printf("INCR counter: %lld\n", reply->integer); freeReplyObject(reply); /* Create a list of numbers, from 0 to 9 */ reply = redictCommand(c,"DEL mylist"); freeReplyObject(reply); for (j = 0; j < 10; j++) { char buf[64]; snprintf(buf,64,"%u",j); reply = redictCommand(c,"LPUSH mylist element-%s", buf); freeReplyObject(reply); } /* Let's check what we have inside the list */ reply = redictCommand(c,"LRANGE mylist 0 -1"); if (reply->type == REDICT_REPLY_ARRAY) { for (j = 0; j < reply->elements; j++) { printf("%u) %s\n", j, reply->element[j]->str); } } freeReplyObject(reply); /* Disconnects and frees the context */ redictFree(c); redictFreeSSLContext(ssl); return 0; } hiredict/examples/example.c000066400000000000000000000104471460333451400162770ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2024 Hiredict Contributors // SPDX-FileCopyrightText: 2024 Salvatore Sanfilippo // // SPDX-License-Identifier: BSD-3-Clause // SPDX-License-Identifier: LGPL-3.0-or-later #include #include #include #include #ifdef _MSC_VER #include /* For struct timeval */ #endif static void example_argv_command(redictContext *c, size_t n) { char **argv, tmp[42]; size_t *argvlen; redictReply *reply; /* We're allocating two additional elements for command and key */ argv = malloc(sizeof(*argv) * (2 + n)); argvlen = malloc(sizeof(*argvlen) * (2 + n)); /* First the command */ argv[0] = (char*)"RPUSH"; argvlen[0] = sizeof("RPUSH") - 1; /* Now our key */ argv[1] = (char*)"argvlist"; argvlen[1] = sizeof("argvlist") - 1; /* Now add the entries we wish to add to the list */ for (size_t i = 2; i < (n + 2); i++) { argvlen[i] = snprintf(tmp, sizeof(tmp), "argv-element-%zu", i - 2); argv[i] = strdup(tmp); } /* Execute the command using redictCommandArgv. We're sending the arguments with * two explicit arrays. One for each argument's string, and the other for its * length. */ reply = redictCommandArgv(c, n + 2, (const char **)argv, (const size_t*)argvlen); if (reply == NULL || c->err) { fprintf(stderr, "Error: Couldn't execute redictCommandArgv\n"); exit(1); } if (reply->type == REDICT_REPLY_INTEGER) { printf("%s reply: %lld\n", argv[0], reply->integer); } freeReplyObject(reply); /* Clean up */ for (size_t i = 2; i < (n + 2); i++) { free(argv[i]); } free(argv); free(argvlen); } int main(int argc, char **argv) { unsigned int j, isunix = 0; redictContext *c; redictReply *reply; const char *hostname = (argc > 1) ? argv[1] : "127.0.0.1"; if (argc > 2) { if (*argv[2] == 'u' || *argv[2] == 'U') { isunix = 1; /* in this case, host is the path to the unix socket */ printf("Will connect to unix socket @%s\n", hostname); } } int port = (argc > 2) ? atoi(argv[2]) : 6379; struct timeval timeout = { 1, 500000 }; // 1.5 seconds if (isunix) { c = redictConnectUnixWithTimeout(hostname, timeout); } else { c = redictConnectWithTimeout(hostname, port, timeout); } if (c == NULL || c->err) { if (c) { printf("Connection error: %s\n", c->errstr); redictFree(c); } else { printf("Connection error: can't allocate redict context\n"); } exit(1); } /* PING server */ reply = redictCommand(c,"PING"); printf("PING: %s\n", reply->str); freeReplyObject(reply); /* Set a key */ reply = redictCommand(c,"SET %s %s", "foo", "hello world"); printf("SET: %s\n", reply->str); freeReplyObject(reply); /* Set a key using binary safe API */ reply = redictCommand(c,"SET %b %b", "bar", (size_t) 3, "hello", (size_t) 5); printf("SET (binary API): %s\n", reply->str); freeReplyObject(reply); /* Try a GET and two INCR */ reply = redictCommand(c,"GET foo"); printf("GET foo: %s\n", reply->str); freeReplyObject(reply); reply = redictCommand(c,"INCR counter"); printf("INCR counter: %lld\n", reply->integer); freeReplyObject(reply); /* again ... */ reply = redictCommand(c,"INCR counter"); printf("INCR counter: %lld\n", reply->integer); freeReplyObject(reply); /* Create a list of numbers, from 0 to 9 */ reply = redictCommand(c,"DEL mylist"); freeReplyObject(reply); for (j = 0; j < 10; j++) { char buf[64]; snprintf(buf,64,"%u",j); reply = redictCommand(c,"LPUSH mylist element-%s", buf); freeReplyObject(reply); } /* Let's check what we have inside the list */ reply = redictCommand(c,"LRANGE mylist 0 -1"); if (reply->type == REDICT_REPLY_ARRAY) { for (j = 0; j < reply->elements; j++) { printf("%u) %s\n", j, reply->element[j]->str); } } freeReplyObject(reply); /* See function for an example of redictCommandArgv */ example_argv_command(c, 10); /* Disconnects and frees the context */ redictFree(c); return 0; } hiredict/fmacros.h000066400000000000000000000010611460333451400144550ustar00rootroot00000000000000/* * Copyright (c) 2009-2011, Salvatore Sanfilippo * * SPDX-FileCopyrightText: 2024 Hiredict Contributors * SPDX-FileCopyrightText: 2024 Salvatore Sanfilippo * * SPDX-License-Identifier: BSD-3-Clause * SPDX-License-Identifier: LGPL-3.0-or-later * */ #ifndef __HIREDICT_FMACRO_H #define __HIREDICT_FMACRO_H #ifndef _AIX #define _XOPEN_SOURCE 600 #define _POSIX_C_SOURCE 200112L #endif #if defined(__APPLE__) && defined(__MACH__) /* Enable TCP_KEEPALIVE */ #define _DARWIN_C_SOURCE #endif #endif hiredict/fuzzing/000077500000000000000000000000001460333451400143505ustar00rootroot00000000000000hiredict/fuzzing/format_command_fuzzer.c000066400000000000000000000022261460333451400211110ustar00rootroot00000000000000/* * Copyright (c) 2020, Salvatore Sanfilippo * Copyright (c) 2020, Pieter Noordhuis * Copyright (c) 2020, Matt Stancliff , * Jan-Erik Rediger * * SPDX-FileCopyrightText: 2024 Hiredict Contributors * SPDX-FileCopyrightText: 2024 Salvatore Sanfilippo * SPDX-FileCopyrightText: 2024 Pieter Noordhuis * SPDX-FileCopyrightText: 2024 Matt Stancliff * SPDX-FileCopyrightText: 2024 Jan-Erik Rediger * * SPDX-License-Identifier: BSD-3-Clause * SPDX-License-Identifier: LGPL-3.0-or-later * */ #include #include #include "hiredict.h" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { char *new_str, *cmd; if (size < 3) return 0; new_str = malloc(size+1); if (new_str == NULL) return 0; memcpy(new_str, data, size); new_str[size] = '\0'; if (redictFormatCommand(&cmd, new_str) != -1) hi_free(cmd); free(new_str); return 0; } hiredict/hiredict-config.cmake.in000066400000000000000000000005321460333451400173210ustar00rootroot00000000000000@PACKAGE_INIT@ set_and_check(hiredict_INCLUDEDIR "@PACKAGE_INCLUDE_INSTALL_DIR@") IF (NOT TARGET hiredict::@hiredict_export_name@) INCLUDE(${CMAKE_CURRENT_LIST_DIR}/hiredict-targets.cmake) ENDIF() SET(hiredict_LIBRARIES hiredict::@hiredict_export_name@) SET(hiredict_INCLUDE_DIRS ${hiredict_INCLUDEDIR}) check_required_components(hiredict) hiredict/hiredict.c000066400000000000000000001074621460333451400146250ustar00rootroot00000000000000/* * Copyright (c) 2009-2011, Salvatore Sanfilippo * Copyright (c) 2010-2014, Pieter Noordhuis * Copyright (c) 2015, Matt Stancliff , * Jan-Erik Rediger * * SPDX-FileCopyrightText: 2024 Hiredict Contributors * SPDX-FileCopyrightText: 2024 Salvatore Sanfilippo * SPDX-FileCopyrightText: 2024 Pieter Noordhuis * SPDX-FileCopyrightText: 2024 Matt Stancliff * SPDX-FileCopyrightText: 2024 Jan-Erik Rediger * * SPDX-License-Identifier: BSD-3-Clause * SPDX-License-Identifier: LGPL-3.0-or-later * */ #include "fmacros.h" #include #include #include #include #include #include "hiredict.h" #include "net.h" #include "sds.h" #include "async.h" #include "win32.h" extern int redictContextUpdateConnectTimeout(redictContext *c, const struct timeval *timeout); extern int redictContextUpdateCommandTimeout(redictContext *c, const struct timeval *timeout); static redictContextFuncs redictContextDefaultFuncs = { .close = redictNetClose, .free_privctx = NULL, .async_read = redictAsyncRead, .async_write = redictAsyncWrite, .read = redictNetRead, .write = redictNetWrite }; static redictReply *createReplyObject(int type); static void *createStringObject(const redictReadTask *task, char *str, size_t len); static void *createArrayObject(const redictReadTask *task, size_t elements); static void *createIntegerObject(const redictReadTask *task, long long value); static void *createDoubleObject(const redictReadTask *task, double value, char *str, size_t len); static void *createNilObject(const redictReadTask *task); static void *createBoolObject(const redictReadTask *task, int bval); /* Default set of functions to build the reply. Keep in mind that such a * function returning NULL is interpreted as OOM. */ static redictReplyObjectFunctions defaultFunctions = { createStringObject, createArrayObject, createIntegerObject, createDoubleObject, createNilObject, createBoolObject, freeReplyObject }; /* Create a reply object */ static redictReply *createReplyObject(int type) { redictReply *r = hi_calloc(1,sizeof(*r)); if (r == NULL) return NULL; r->type = type; return r; } /* Free a reply object */ void freeReplyObject(void *reply) { redictReply *r = reply; size_t j; if (r == NULL) return; switch(r->type) { case REDICT_REPLY_INTEGER: case REDICT_REPLY_NIL: case REDICT_REPLY_BOOL: break; /* Nothing to free */ case REDICT_REPLY_ARRAY: case REDICT_REPLY_MAP: case REDICT_REPLY_ATTR: case REDICT_REPLY_SET: case REDICT_REPLY_PUSH: if (r->element != NULL) { for (j = 0; j < r->elements; j++) freeReplyObject(r->element[j]); hi_free(r->element); } break; case REDICT_REPLY_ERROR: case REDICT_REPLY_STATUS: case REDICT_REPLY_STRING: case REDICT_REPLY_DOUBLE: case REDICT_REPLY_VERB: case REDICT_REPLY_BIGNUM: hi_free(r->str); break; } hi_free(r); } static void *createStringObject(const redictReadTask *task, char *str, size_t len) { redictReply *r, *parent; char *buf; r = createReplyObject(task->type); if (r == NULL) return NULL; assert(task->type == REDICT_REPLY_ERROR || task->type == REDICT_REPLY_STATUS || task->type == REDICT_REPLY_STRING || task->type == REDICT_REPLY_VERB || task->type == REDICT_REPLY_BIGNUM); /* Copy string value */ if (task->type == REDICT_REPLY_VERB) { buf = hi_malloc(len-4+1); /* Skip 4 bytes of verbatim type header. */ if (buf == NULL) goto oom; memcpy(r->vtype,str,3); r->vtype[3] = '\0'; memcpy(buf,str+4,len-4); buf[len-4] = '\0'; r->len = len - 4; } else { buf = hi_malloc(len+1); if (buf == NULL) goto oom; memcpy(buf,str,len); buf[len] = '\0'; r->len = len; } r->str = buf; if (task->parent) { parent = task->parent->obj; assert(parent->type == REDICT_REPLY_ARRAY || parent->type == REDICT_REPLY_MAP || parent->type == REDICT_REPLY_ATTR || parent->type == REDICT_REPLY_SET || parent->type == REDICT_REPLY_PUSH); parent->element[task->idx] = r; } return r; oom: freeReplyObject(r); return NULL; } static void *createArrayObject(const redictReadTask *task, size_t elements) { redictReply *r, *parent; r = createReplyObject(task->type); if (r == NULL) return NULL; if (elements > 0) { r->element = hi_calloc(elements,sizeof(redictReply*)); if (r->element == NULL) { freeReplyObject(r); return NULL; } } r->elements = elements; if (task->parent) { parent = task->parent->obj; assert(parent->type == REDICT_REPLY_ARRAY || parent->type == REDICT_REPLY_MAP || parent->type == REDICT_REPLY_ATTR || parent->type == REDICT_REPLY_SET || parent->type == REDICT_REPLY_PUSH); parent->element[task->idx] = r; } return r; } static void *createIntegerObject(const redictReadTask *task, long long value) { redictReply *r, *parent; r = createReplyObject(REDICT_REPLY_INTEGER); if (r == NULL) return NULL; r->integer = value; if (task->parent) { parent = task->parent->obj; assert(parent->type == REDICT_REPLY_ARRAY || parent->type == REDICT_REPLY_MAP || parent->type == REDICT_REPLY_ATTR || parent->type == REDICT_REPLY_SET || parent->type == REDICT_REPLY_PUSH); parent->element[task->idx] = r; } return r; } static void *createDoubleObject(const redictReadTask *task, double value, char *str, size_t len) { redictReply *r, *parent; if (len == SIZE_MAX) // Prevents hi_malloc(0) if len equals to SIZE_MAX return NULL; r = createReplyObject(REDICT_REPLY_DOUBLE); if (r == NULL) return NULL; r->dval = value; r->str = hi_malloc(len+1); if (r->str == NULL) { freeReplyObject(r); return NULL; } /* The double reply also has the original protocol string representing a * double as a null terminated string. This way the caller does not need * to format back for string conversion, especially since Redict does efforts * to make the string more human readable avoiding the calssical double * decimal string conversion artifacts. */ memcpy(r->str, str, len); r->str[len] = '\0'; r->len = len; if (task->parent) { parent = task->parent->obj; assert(parent->type == REDICT_REPLY_ARRAY || parent->type == REDICT_REPLY_MAP || parent->type == REDICT_REPLY_ATTR || parent->type == REDICT_REPLY_SET || parent->type == REDICT_REPLY_PUSH); parent->element[task->idx] = r; } return r; } static void *createNilObject(const redictReadTask *task) { redictReply *r, *parent; r = createReplyObject(REDICT_REPLY_NIL); if (r == NULL) return NULL; if (task->parent) { parent = task->parent->obj; assert(parent->type == REDICT_REPLY_ARRAY || parent->type == REDICT_REPLY_MAP || parent->type == REDICT_REPLY_ATTR || parent->type == REDICT_REPLY_SET || parent->type == REDICT_REPLY_PUSH); parent->element[task->idx] = r; } return r; } static void *createBoolObject(const redictReadTask *task, int bval) { redictReply *r, *parent; r = createReplyObject(REDICT_REPLY_BOOL); if (r == NULL) return NULL; r->integer = bval != 0; if (task->parent) { parent = task->parent->obj; assert(parent->type == REDICT_REPLY_ARRAY || parent->type == REDICT_REPLY_MAP || parent->type == REDICT_REPLY_ATTR || parent->type == REDICT_REPLY_SET || parent->type == REDICT_REPLY_PUSH); parent->element[task->idx] = r; } return r; } /* Return the number of digits of 'v' when converted to string in radix 10. * Implementation borrowed from link in redict/src/util.c:string2ll(). */ static uint32_t countDigits(uint64_t v) { uint32_t result = 1; for (;;) { if (v < 10) return result; if (v < 100) return result + 1; if (v < 1000) return result + 2; if (v < 10000) return result + 3; v /= 10000U; result += 4; } } /* Helper that calculates the bulk length given a certain string length. */ static size_t bulklen(size_t len) { return 1+countDigits(len)+2+len+2; } int redictvFormatCommand(char **target, const char *format, va_list ap) { const char *c = format; char *cmd = NULL; /* final command */ int pos; /* position in final command */ sds curarg, newarg; /* current argument */ int touched = 0; /* was the current argument touched? */ char **curargv = NULL, **newargv = NULL; int argc = 0; int totlen = 0; int error_type = 0; /* 0 = no error; -1 = memory error; -2 = format error */ int j; /* Abort if there is not target to set */ if (target == NULL) return -1; /* Build the command string accordingly to protocol */ curarg = sdsempty(); if (curarg == NULL) return -1; while(*c != '\0') { if (*c != '%' || c[1] == '\0') { if (*c == ' ') { if (touched) { newargv = hi_realloc(curargv,sizeof(char*)*(argc+1)); if (newargv == NULL) goto memory_err; curargv = newargv; curargv[argc++] = curarg; totlen += bulklen(sdslen(curarg)); /* curarg is put in argv so it can be overwritten. */ curarg = sdsempty(); if (curarg == NULL) goto memory_err; touched = 0; } } else { newarg = sdscatlen(curarg,c,1); if (newarg == NULL) goto memory_err; curarg = newarg; touched = 1; } } else { char *arg; size_t size; /* Set newarg so it can be checked even if it is not touched. */ newarg = curarg; switch(c[1]) { case 's': arg = va_arg(ap,char*); size = strlen(arg); if (size > 0) newarg = sdscatlen(curarg,arg,size); break; case 'b': arg = va_arg(ap,char*); size = va_arg(ap,size_t); if (size > 0) newarg = sdscatlen(curarg,arg,size); break; case '%': newarg = sdscat(curarg,"%"); break; default: /* Try to detect printf format */ { static const char intfmts[] = "diouxX"; static const char flags[] = "#0-+ "; char _format[16]; const char *_p = c+1; size_t _l = 0; va_list _cpy; /* Flags */ while (*_p != '\0' && strchr(flags,*_p) != NULL) _p++; /* Field width */ while (*_p != '\0' && isdigit((int) *_p)) _p++; /* Precision */ if (*_p == '.') { _p++; while (*_p != '\0' && isdigit((int) *_p)) _p++; } /* Copy va_list before consuming with va_arg */ va_copy(_cpy,ap); /* Make sure we have more characters otherwise strchr() accepts * '\0' as an integer specifier. This is checked after above * va_copy() to avoid UB in fmt_invalid's call to va_end(). */ if (*_p == '\0') goto fmt_invalid; /* Integer conversion (without modifiers) */ if (strchr(intfmts,*_p) != NULL) { va_arg(ap,int); goto fmt_valid; } /* Double conversion (without modifiers) */ if (strchr("eEfFgGaA",*_p) != NULL) { va_arg(ap,double); goto fmt_valid; } /* Size: char */ if (_p[0] == 'h' && _p[1] == 'h') { _p += 2; if (*_p != '\0' && strchr(intfmts,*_p) != NULL) { va_arg(ap,int); /* char gets promoted to int */ goto fmt_valid; } goto fmt_invalid; } /* Size: short */ if (_p[0] == 'h') { _p += 1; if (*_p != '\0' && strchr(intfmts,*_p) != NULL) { va_arg(ap,int); /* short gets promoted to int */ goto fmt_valid; } goto fmt_invalid; } /* Size: long long */ if (_p[0] == 'l' && _p[1] == 'l') { _p += 2; if (*_p != '\0' && strchr(intfmts,*_p) != NULL) { va_arg(ap,long long); goto fmt_valid; } goto fmt_invalid; } /* Size: long */ if (_p[0] == 'l') { _p += 1; if (*_p != '\0' && strchr(intfmts,*_p) != NULL) { va_arg(ap,long); goto fmt_valid; } goto fmt_invalid; } fmt_invalid: va_end(_cpy); goto format_err; fmt_valid: _l = (_p+1)-c; if (_l < sizeof(_format)-2) { memcpy(_format,c,_l); _format[_l] = '\0'; newarg = sdscatvprintf(curarg,_format,_cpy); /* Update current position (note: outer blocks * increment c twice so compensate here) */ c = _p-1; } va_end(_cpy); break; } } if (newarg == NULL) goto memory_err; curarg = newarg; touched = 1; c++; if (*c == '\0') break; } c++; } /* Add the last argument if needed */ if (touched) { newargv = hi_realloc(curargv,sizeof(char*)*(argc+1)); if (newargv == NULL) goto memory_err; curargv = newargv; curargv[argc++] = curarg; totlen += bulklen(sdslen(curarg)); } else { sdsfree(curarg); } /* Clear curarg because it was put in curargv or was free'd. */ curarg = NULL; /* Add bytes needed to hold multi bulk count */ totlen += 1+countDigits(argc)+2; /* Build the command at protocol level */ cmd = hi_malloc(totlen+1); if (cmd == NULL) goto memory_err; pos = sprintf(cmd,"*%d\r\n",argc); for (j = 0; j < argc; j++) { pos += sprintf(cmd+pos,"$%zu\r\n",sdslen(curargv[j])); memcpy(cmd+pos,curargv[j],sdslen(curargv[j])); pos += sdslen(curargv[j]); sdsfree(curargv[j]); cmd[pos++] = '\r'; cmd[pos++] = '\n'; } assert(pos == totlen); cmd[pos] = '\0'; hi_free(curargv); *target = cmd; return totlen; format_err: error_type = -2; goto cleanup; memory_err: error_type = -1; goto cleanup; cleanup: if (curargv) { while(argc--) sdsfree(curargv[argc]); hi_free(curargv); } sdsfree(curarg); hi_free(cmd); return error_type; } /* Format a command according to the Redict protocol. This function * takes a format similar to printf: * * %s represents a C null terminated string you want to interpolate * %b represents a binary safe string * * When using %b you need to provide both the pointer to the string * and the length in bytes as a size_t. Examples: * * len = redictFormatCommand(target, "GET %s", mykey); * len = redictFormatCommand(target, "SET %s %b", mykey, myval, myvallen); */ int redictFormatCommand(char **target, const char *format, ...) { va_list ap; int len; va_start(ap,format); len = redictvFormatCommand(target,format,ap); va_end(ap); /* The API says "-1" means bad result, but we now also return "-2" in some * cases. Force the return value to always be -1. */ if (len < 0) len = -1; return len; } /* Format a command according to the Redict protocol using an sds string and * sdscatfmt for the processing of arguments. This function takes the * number of arguments, an array with arguments and an array with their * lengths. If the latter is set to NULL, strlen will be used to compute the * argument lengths. */ long long redictFormatSdsCommandArgv(sds *target, int argc, const char **argv, const size_t *argvlen) { sds cmd, aux; unsigned long long totlen, len; int j; /* Abort on a NULL target */ if (target == NULL) return -1; /* Calculate our total size */ totlen = 1+countDigits(argc)+2; for (j = 0; j < argc; j++) { len = argvlen ? argvlen[j] : strlen(argv[j]); totlen += bulklen(len); } /* Use an SDS string for command construction */ cmd = sdsempty(); if (cmd == NULL) return -1; /* We already know how much storage we need */ aux = sdsMakeRoomFor(cmd, totlen); if (aux == NULL) { sdsfree(cmd); return -1; } cmd = aux; /* Construct command */ cmd = sdscatfmt(cmd, "*%i\r\n", argc); for (j=0; j < argc; j++) { len = argvlen ? argvlen[j] : strlen(argv[j]); cmd = sdscatfmt(cmd, "$%U\r\n", len); cmd = sdscatlen(cmd, argv[j], len); cmd = sdscatlen(cmd, "\r\n", sizeof("\r\n")-1); } assert(sdslen(cmd)==totlen); *target = cmd; return totlen; } void redictFreeSdsCommand(sds cmd) { sdsfree(cmd); } /* Format a command according to the Redict protocol. This function takes the * number of arguments, an array with arguments and an array with their * lengths. If the latter is set to NULL, strlen will be used to compute the * argument lengths. */ long long redictFormatCommandArgv(char **target, int argc, const char **argv, const size_t *argvlen) { char *cmd = NULL; /* final command */ size_t pos; /* position in final command */ size_t len, totlen; int j; /* Abort on a NULL target */ if (target == NULL) return -1; /* Calculate number of bytes needed for the command */ totlen = 1+countDigits(argc)+2; for (j = 0; j < argc; j++) { len = argvlen ? argvlen[j] : strlen(argv[j]); totlen += bulklen(len); } /* Build the command at protocol level */ cmd = hi_malloc(totlen+1); if (cmd == NULL) return -1; pos = sprintf(cmd,"*%d\r\n",argc); for (j = 0; j < argc; j++) { len = argvlen ? argvlen[j] : strlen(argv[j]); pos += sprintf(cmd+pos,"$%zu\r\n",len); memcpy(cmd+pos,argv[j],len); pos += len; cmd[pos++] = '\r'; cmd[pos++] = '\n'; } assert(pos == totlen); cmd[pos] = '\0'; *target = cmd; return totlen; } void redictFreeCommand(char *cmd) { hi_free(cmd); } void __redictSetError(redictContext *c, int type, const char *str) { size_t len; c->err = type; if (str != NULL) { len = strlen(str); len = len < (sizeof(c->errstr)-1) ? len : (sizeof(c->errstr)-1); memcpy(c->errstr,str,len); c->errstr[len] = '\0'; } else { /* Only REDICT_ERR_IO may lack a description! */ assert(type == REDICT_ERR_IO); strerror_r(errno, c->errstr, sizeof(c->errstr)); } } redictReader *redictReaderCreate(void) { return redictReaderCreateWithFunctions(&defaultFunctions); } static void redictPushAutoFree(void *privdata, void *reply) { (void)privdata; freeReplyObject(reply); } static redictContext *redictContextInit(void) { redictContext *c; c = hi_calloc(1, sizeof(*c)); if (c == NULL) return NULL; c->funcs = &redictContextDefaultFuncs; c->obuf = sdsempty(); c->reader = redictReaderCreate(); c->fd = REDICT_INVALID_FD; if (c->obuf == NULL || c->reader == NULL) { redictFree(c); return NULL; } return c; } void redictFree(redictContext *c) { if (c == NULL) return; if (c->funcs && c->funcs->close) { c->funcs->close(c); } sdsfree(c->obuf); redictReaderFree(c->reader); hi_free(c->tcp.host); hi_free(c->tcp.source_addr); hi_free(c->unix_sock.path); hi_free(c->connect_timeout); hi_free(c->command_timeout); hi_free(c->saddr); if (c->privdata && c->free_privdata) c->free_privdata(c->privdata); if (c->funcs && c->funcs->free_privctx) c->funcs->free_privctx(c->privctx); memset(c, 0xff, sizeof(*c)); hi_free(c); } redictFD redictFreeKeepFd(redictContext *c) { redictFD fd = c->fd; c->fd = REDICT_INVALID_FD; redictFree(c); return fd; } int redictReconnect(redictContext *c) { c->err = 0; memset(c->errstr, '\0', strlen(c->errstr)); if (c->privctx && c->funcs->free_privctx) { c->funcs->free_privctx(c->privctx); c->privctx = NULL; } if (c->funcs && c->funcs->close) { c->funcs->close(c); } sdsfree(c->obuf); redictReaderFree(c->reader); c->obuf = sdsempty(); c->reader = redictReaderCreate(); if (c->obuf == NULL || c->reader == NULL) { __redictSetError(c, REDICT_ERR_OOM, "Out of memory"); return REDICT_ERR; } int ret = REDICT_ERR; if (c->connection_type == REDICT_CONN_TCP) { ret = redictContextConnectBindTcp(c, c->tcp.host, c->tcp.port, c->connect_timeout, c->tcp.source_addr); } else if (c->connection_type == REDICT_CONN_UNIX) { ret = redictContextConnectUnix(c, c->unix_sock.path, c->connect_timeout); } else { /* Something bad happened here and shouldn't have. There isn't enough information in the context to reconnect. */ __redictSetError(c,REDICT_ERR_OTHER,"Not enough information to reconnect"); ret = REDICT_ERR; } if (c->command_timeout != NULL && (c->flags & REDICT_BLOCK) && c->fd != REDICT_INVALID_FD) { redictContextSetTimeout(c, *c->command_timeout); } return ret; } redictContext *redictConnectWithOptions(const redictOptions *options) { redictContext *c = redictContextInit(); if (c == NULL) { return NULL; } if (!(options->options & REDICT_OPT_NONBLOCK)) { c->flags |= REDICT_BLOCK; } if (options->options & REDICT_OPT_REUSEADDR) { c->flags |= REDICT_REUSEADDR; } if (options->options & REDICT_OPT_NOAUTOFREE) { c->flags |= REDICT_NO_AUTO_FREE; } if (options->options & REDICT_OPT_NOAUTOFREEREPLIES) { c->flags |= REDICT_NO_AUTO_FREE_REPLIES; } if (options->options & REDICT_OPT_PREFER_IPV4) { c->flags |= REDICT_PREFER_IPV4; } if (options->options & REDICT_OPT_PREFER_IPV6) { c->flags |= REDICT_PREFER_IPV6; } /* Set any user supplied RESP3 PUSH handler or use freeReplyObject * as a default unless specifically flagged that we don't want one. */ if (options->push_cb != NULL) redictSetPushCallback(c, options->push_cb); else if (!(options->options & REDICT_OPT_NO_PUSH_AUTOFREE)) redictSetPushCallback(c, redictPushAutoFree); c->privdata = options->privdata; c->free_privdata = options->free_privdata; if (redictContextUpdateConnectTimeout(c, options->connect_timeout) != REDICT_OK || redictContextUpdateCommandTimeout(c, options->command_timeout) != REDICT_OK) { __redictSetError(c, REDICT_ERR_OOM, "Out of memory"); return c; } if (options->type == REDICT_CONN_TCP) { redictContextConnectBindTcp(c, options->endpoint.tcp.ip, options->endpoint.tcp.port, options->connect_timeout, options->endpoint.tcp.source_addr); } else if (options->type == REDICT_CONN_UNIX) { redictContextConnectUnix(c, options->endpoint.unix_socket, options->connect_timeout); } else if (options->type == REDICT_CONN_USERFD) { c->fd = options->endpoint.fd; c->flags |= REDICT_CONNECTED; } else { redictFree(c); return NULL; } if (c->err == 0 && c->fd != REDICT_INVALID_FD && options->command_timeout != NULL && (c->flags & REDICT_BLOCK)) { redictContextSetTimeout(c, *options->command_timeout); } return c; } /* Connect to a Redict instance. On error the field error in the returned * context will be set to the return value of the error function. * When no set of reply functions is given, the default set will be used. */ redictContext *redictConnect(const char *ip, int port) { redictOptions options = {0}; REDICT_OPTIONS_SET_TCP(&options, ip, port); return redictConnectWithOptions(&options); } redictContext *redictConnectWithTimeout(const char *ip, int port, const struct timeval tv) { redictOptions options = {0}; REDICT_OPTIONS_SET_TCP(&options, ip, port); options.connect_timeout = &tv; return redictConnectWithOptions(&options); } redictContext *redictConnectNonBlock(const char *ip, int port) { redictOptions options = {0}; REDICT_OPTIONS_SET_TCP(&options, ip, port); options.options |= REDICT_OPT_NONBLOCK; return redictConnectWithOptions(&options); } redictContext *redictConnectBindNonBlock(const char *ip, int port, const char *source_addr) { redictOptions options = {0}; REDICT_OPTIONS_SET_TCP(&options, ip, port); options.endpoint.tcp.source_addr = source_addr; options.options |= REDICT_OPT_NONBLOCK; return redictConnectWithOptions(&options); } redictContext *redictConnectBindNonBlockWithReuse(const char *ip, int port, const char *source_addr) { redictOptions options = {0}; REDICT_OPTIONS_SET_TCP(&options, ip, port); options.endpoint.tcp.source_addr = source_addr; options.options |= REDICT_OPT_NONBLOCK|REDICT_OPT_REUSEADDR; return redictConnectWithOptions(&options); } redictContext *redictConnectUnix(const char *path) { redictOptions options = {0}; REDICT_OPTIONS_SET_UNIX(&options, path); return redictConnectWithOptions(&options); } redictContext *redictConnectUnixWithTimeout(const char *path, const struct timeval tv) { redictOptions options = {0}; REDICT_OPTIONS_SET_UNIX(&options, path); options.connect_timeout = &tv; return redictConnectWithOptions(&options); } redictContext *redictConnectUnixNonBlock(const char *path) { redictOptions options = {0}; REDICT_OPTIONS_SET_UNIX(&options, path); options.options |= REDICT_OPT_NONBLOCK; return redictConnectWithOptions(&options); } redictContext *redictConnectFd(redictFD fd) { redictOptions options = {0}; options.type = REDICT_CONN_USERFD; options.endpoint.fd = fd; return redictConnectWithOptions(&options); } /* Set read/write timeout on a blocking socket. */ int redictSetTimeout(redictContext *c, const struct timeval tv) { if (c->flags & REDICT_BLOCK) return redictContextSetTimeout(c,tv); return REDICT_ERR; } int redictEnableKeepAliveWithInterval(redictContext *c, int interval) { return redictKeepAlive(c, interval); } /* Enable connection KeepAlive. */ int redictEnableKeepAlive(redictContext *c) { return redictKeepAlive(c, REDICT_KEEPALIVE_INTERVAL); } /* Set the socket option TCP_USER_TIMEOUT. */ int redictSetTcpUserTimeout(redictContext *c, unsigned int timeout) { return redictContextSetTcpUserTimeout(c, timeout); } /* Set a user provided RESP3 PUSH handler and return any old one set. */ redictPushFn *redictSetPushCallback(redictContext *c, redictPushFn *fn) { redictPushFn *old = c->push_cb; c->push_cb = fn; return old; } /* Use this function to handle a read event on the descriptor. It will try * and read some bytes from the socket and feed them to the reply parser. * * After this function is called, you may use redictGetReplyFromReader to * see if there is a reply available. */ int redictBufferRead(redictContext *c) { char buf[1024*16]; int nread; /* Return early when the context has seen an error. */ if (c->err) return REDICT_ERR; nread = c->funcs->read(c, buf, sizeof(buf)); if (nread < 0) { return REDICT_ERR; } if (nread > 0 && redictReaderFeed(c->reader, buf, nread) != REDICT_OK) { __redictSetError(c, c->reader->err, c->reader->errstr); return REDICT_ERR; } return REDICT_OK; } /* Write the output buffer to the socket. * * Returns REDICT_OK when the buffer is empty, or (a part of) the buffer was * successfully written to the socket. When the buffer is empty after the * write operation, "done" is set to 1 (if given). * * Returns REDICT_ERR if an unrecoverable error occurred in the underlying * c->funcs->write function. */ int redictBufferWrite(redictContext *c, int *done) { /* Return early when the context has seen an error. */ if (c->err) return REDICT_ERR; if (sdslen(c->obuf) > 0) { ssize_t nwritten = c->funcs->write(c); if (nwritten < 0) { return REDICT_ERR; } else if (nwritten > 0) { if (nwritten == (ssize_t)sdslen(c->obuf)) { sdsfree(c->obuf); c->obuf = sdsempty(); if (c->obuf == NULL) goto oom; } else { if (sdsrange(c->obuf,nwritten,-1) < 0) goto oom; } } } if (done != NULL) *done = (sdslen(c->obuf) == 0); return REDICT_OK; oom: __redictSetError(c, REDICT_ERR_OOM, "Out of memory"); return REDICT_ERR; } /* Internal helper that returns 1 if the reply was a RESP3 PUSH * message and we handled it with a user-provided callback. */ static int redictHandledPushReply(redictContext *c, void *reply) { if (reply && c->push_cb && redictIsPushReply(reply)) { c->push_cb(c->privdata, reply); return 1; } return 0; } /* Get a reply from our reader or set an error in the context. */ int redictGetReplyFromReader(redictContext *c, void **reply) { if (redictReaderGetReply(c->reader, reply) == REDICT_ERR) { __redictSetError(c,c->reader->err,c->reader->errstr); return REDICT_ERR; } return REDICT_OK; } /* Internal helper to get the next reply from our reader while handling * any PUSH messages we encounter along the way. This is separate from * redictGetReplyFromReader so as to not change its behavior. */ static int redictNextInBandReplyFromReader(redictContext *c, void **reply) { do { if (redictGetReplyFromReader(c, reply) == REDICT_ERR) return REDICT_ERR; } while (redictHandledPushReply(c, *reply)); return REDICT_OK; } int redictGetReply(redictContext *c, void **reply) { int wdone = 0; void *aux = NULL; /* Try to read pending replies */ if (redictNextInBandReplyFromReader(c,&aux) == REDICT_ERR) return REDICT_ERR; /* For the blocking context, flush output buffer and read reply */ if (aux == NULL && c->flags & REDICT_BLOCK) { /* Write until done */ do { if (redictBufferWrite(c,&wdone) == REDICT_ERR) return REDICT_ERR; } while (!wdone); /* Read until there is a reply */ do { if (redictBufferRead(c) == REDICT_ERR) return REDICT_ERR; if (redictNextInBandReplyFromReader(c,&aux) == REDICT_ERR) return REDICT_ERR; } while (aux == NULL); } /* Set reply or free it if we were passed NULL */ if (reply != NULL) { *reply = aux; } else { freeReplyObject(aux); } return REDICT_OK; } /* Helper function for the redictAppendCommand* family of functions. * * Write a formatted command to the output buffer. When this family * is used, you need to call redictGetReply yourself to retrieve * the reply (or replies in pub/sub). */ int __redictAppendCommand(redictContext *c, const char *cmd, size_t len) { sds newbuf; newbuf = sdscatlen(c->obuf,cmd,len); if (newbuf == NULL) { __redictSetError(c,REDICT_ERR_OOM,"Out of memory"); return REDICT_ERR; } c->obuf = newbuf; return REDICT_OK; } int redictAppendFormattedCommand(redictContext *c, const char *cmd, size_t len) { if (__redictAppendCommand(c, cmd, len) != REDICT_OK) { return REDICT_ERR; } return REDICT_OK; } int redictvAppendCommand(redictContext *c, const char *format, va_list ap) { char *cmd; int len; len = redictvFormatCommand(&cmd,format,ap); if (len == -1) { __redictSetError(c,REDICT_ERR_OOM,"Out of memory"); return REDICT_ERR; } else if (len == -2) { __redictSetError(c,REDICT_ERR_OTHER,"Invalid format string"); return REDICT_ERR; } if (__redictAppendCommand(c,cmd,len) != REDICT_OK) { hi_free(cmd); return REDICT_ERR; } hi_free(cmd); return REDICT_OK; } int redictAppendCommand(redictContext *c, const char *format, ...) { va_list ap; int ret; va_start(ap,format); ret = redictvAppendCommand(c,format,ap); va_end(ap); return ret; } int redictAppendCommandArgv(redictContext *c, int argc, const char **argv, const size_t *argvlen) { sds cmd; long long len; len = redictFormatSdsCommandArgv(&cmd,argc,argv,argvlen); if (len == -1) { __redictSetError(c,REDICT_ERR_OOM,"Out of memory"); return REDICT_ERR; } if (__redictAppendCommand(c,cmd,len) != REDICT_OK) { sdsfree(cmd); return REDICT_ERR; } sdsfree(cmd); return REDICT_OK; } /* Helper function for the redictCommand* family of functions. * * Write a formatted command to the output buffer. If the given context is * blocking, immediately read the reply into the "reply" pointer. When the * context is non-blocking, the "reply" pointer will not be used and the * command is simply appended to the write buffer. * * Returns the reply when a reply was successfully retrieved. Returns NULL * otherwise. When NULL is returned in a blocking context, the error field * in the context will be set. */ static void *__redictBlockForReply(redictContext *c) { void *reply; if (c->flags & REDICT_BLOCK) { if (redictGetReply(c,&reply) != REDICT_OK) return NULL; return reply; } return NULL; } void *redictvCommand(redictContext *c, const char *format, va_list ap) { if (redictvAppendCommand(c,format,ap) != REDICT_OK) return NULL; return __redictBlockForReply(c); } void *redictCommand(redictContext *c, const char *format, ...) { va_list ap; va_start(ap,format); void *reply = redictvCommand(c,format,ap); va_end(ap); return reply; } void *redictCommandArgv(redictContext *c, int argc, const char **argv, const size_t *argvlen) { if (redictAppendCommandArgv(c,argc,argv,argvlen) != REDICT_OK) return NULL; return __redictBlockForReply(c); } hiredict/hiredict.h000066400000000000000000000321601460333451400146220ustar00rootroot00000000000000/* * Copyright (c) 2009-2011, Salvatore Sanfilippo * Copyright (c) 2010-2014, Pieter Noordhuis * Copyright (c) 2015, Matt Stancliff , * Jan-Erik Rediger * * SPDX-FileCopyrightText: 2024 Hiredict Contributors * SPDX-FileCopyrightText: 2024 Salvatore Sanfilippo * SPDX-FileCopyrightText: 2024 Pieter Noordhuis * SPDX-FileCopyrightText: 2024 Matt Stancliff * SPDX-FileCopyrightText: 2024 Jan-Erik Rediger * * SPDX-License-Identifier: BSD-3-Clause * SPDX-License-Identifier: LGPL-3.0-or-later * */ #ifndef __HIREDICT_H #define __HIREDICT_H #include "read.h" #include /* for va_list */ #ifndef _MSC_VER #include /* for struct timeval */ #else struct timeval; /* forward declaration */ typedef long long ssize_t; #endif #include /* uintXX_t, etc */ #include "sds.h" /* for sds */ #include "alloc.h" /* for allocation wrappers */ #define HIREDICT_MAJOR 1 #define HIREDICT_MINOR 3 #define HIREDICT_PATCH 1 #define HIREDICT_SONAME 1.3.1 /* Connection type can be blocking or non-blocking and is set in the * least significant bit of the flags field in redictContext. */ #define REDICT_BLOCK 0x1 /* Connection may be disconnected before being free'd. The second bit * in the flags field is set when the context is connected. */ #define REDICT_CONNECTED 0x2 /* The async API might try to disconnect cleanly and flush the output * buffer and read all subsequent replies before disconnecting. * This flag means no new commands can come in and the connection * should be terminated once all replies have been read. */ #define REDICT_DISCONNECTING 0x4 /* Flag specific to the async API which means that the context should be clean * up as soon as possible. */ #define REDICT_FREEING 0x8 /* Flag that is set when an async callback is executed. */ #define REDICT_IN_CALLBACK 0x10 /* Flag that is set when the async context has one or more subscriptions. */ #define REDICT_SUBSCRIBED 0x20 /* Flag that is set when monitor mode is active */ #define REDICT_MONITORING 0x40 /* Flag that is set when we should set SO_REUSEADDR before calling bind() */ #define REDICT_REUSEADDR 0x80 /* Flag that is set when the async connection supports push replies. */ #define REDICT_SUPPORTS_PUSH 0x100 /** * Flag that indicates the user does not want the context to * be automatically freed upon error */ #define REDICT_NO_AUTO_FREE 0x200 /* Flag that indicates the user does not want replies to be automatically freed */ #define REDICT_NO_AUTO_FREE_REPLIES 0x400 /* Flags to prefer IPv6 or IPv4 when doing DNS lookup. (If both are set, * AF_UNSPEC is used.) */ #define REDICT_PREFER_IPV4 0x800 #define REDICT_PREFER_IPV6 0x1000 #define REDICT_KEEPALIVE_INTERVAL 15 /* seconds */ /* number of times we retry to connect in the case of EADDRNOTAVAIL and * SO_REUSEADDR is being used. */ #define REDICT_CONNECT_RETRIES 10 /* Forward declarations for structs defined elsewhere */ struct redictAsyncContext; struct redictContext; /* RESP3 push helpers and callback prototypes */ #define redictIsPushReply(r) (((redictReply*)(r))->type == REDICT_REPLY_PUSH) typedef void (redictPushFn)(void *, void *); typedef void (redictAsyncPushFn)(struct redictAsyncContext *, void *); #ifdef __cplusplus extern "C" { #endif /* This is the reply object returned by redictCommand() */ typedef struct redictReply { int type; /* REDICT_REPLY_* */ long long integer; /* The integer when type is REDICT_REPLY_INTEGER */ double dval; /* The double when type is REDICT_REPLY_DOUBLE */ size_t len; /* Length of string */ char *str; /* Used for REDICT_REPLY_ERROR, REDICT_REPLY_STRING REDICT_REPLY_VERB, REDICT_REPLY_DOUBLE (in additional to dval), and REDICT_REPLY_BIGNUM. */ char vtype[4]; /* Used for REDICT_REPLY_VERB, contains the null terminated 3 character content type, such as "txt". */ size_t elements; /* number of elements, for REDICT_REPLY_ARRAY */ struct redictReply **element; /* elements vector for REDICT_REPLY_ARRAY */ } redictReply; redictReader *redictReaderCreate(void); /* Function to free the reply objects hiredict returns by default. */ void freeReplyObject(void *reply); /* Functions to format a command according to the protocol. */ int redictvFormatCommand(char **target, const char *format, va_list ap); int redictFormatCommand(char **target, const char *format, ...); long long redictFormatCommandArgv(char **target, int argc, const char **argv, const size_t *argvlen); long long redictFormatSdsCommandArgv(sds *target, int argc, const char ** argv, const size_t *argvlen); void redictFreeCommand(char *cmd); void redictFreeSdsCommand(sds cmd); enum redictConnectionType { REDICT_CONN_TCP, REDICT_CONN_UNIX, REDICT_CONN_USERFD }; struct redictSsl; #define REDICT_OPT_NONBLOCK 0x01 #define REDICT_OPT_REUSEADDR 0x02 #define REDICT_OPT_NOAUTOFREE 0x04 /* Don't automatically free the async * object on a connection failure, or * other implicit conditions. Only free * on an explicit call to disconnect() * or free() */ #define REDICT_OPT_NO_PUSH_AUTOFREE 0x08 /* Don't automatically intercept and * free RESP3 PUSH replies. */ #define REDICT_OPT_NOAUTOFREEREPLIES 0x10 /* Don't automatically free replies. */ #define REDICT_OPT_PREFER_IPV4 0x20 /* Prefer IPv4 in DNS lookups. */ #define REDICT_OPT_PREFER_IPV6 0x40 /* Prefer IPv6 in DNS lookups. */ #define REDICT_OPT_PREFER_IP_UNSPEC (REDICT_OPT_PREFER_IPV4 | REDICT_OPT_PREFER_IPV6) /* In Unix systems a file descriptor is a regular signed int, with -1 * representing an invalid descriptor. In Windows it is a SOCKET * (32- or 64-bit unsigned integer depending on the architecture), where * all bits set (~0) is INVALID_SOCKET. */ #ifndef _WIN32 typedef int redictFD; #define REDICT_INVALID_FD -1 #else #ifdef _WIN64 typedef unsigned long long redictFD; /* SOCKET = 64-bit UINT_PTR */ #else typedef unsigned long redictFD; /* SOCKET = 32-bit UINT_PTR */ #endif #define REDICT_INVALID_FD ((redictFD)(~0)) /* INVALID_SOCKET */ #endif typedef struct { /* * the type of connection to use. This also indicates which * `endpoint` member field to use */ int type; /* bit field of REDICT_OPT_xxx */ int options; /* timeout value for connect operation. If NULL, no timeout is used */ const struct timeval *connect_timeout; /* timeout value for commands. If NULL, no timeout is used. This can be * updated at runtime with redictSetTimeout/redictAsyncSetTimeout. */ const struct timeval *command_timeout; union { /** use this field for tcp/ip connections */ struct { const char *source_addr; const char *ip; int port; } tcp; /** use this field for unix domain sockets */ const char *unix_socket; /** * use this field to have hiredict operate an already-open * file descriptor */ redictFD fd; } endpoint; /* Optional user defined data/destructor */ void *privdata; void (*free_privdata)(void *); /* A user defined PUSH message callback */ redictPushFn *push_cb; redictAsyncPushFn *async_push_cb; } redictOptions; /** * Helper macros to initialize options to their specified fields. */ #define REDICT_OPTIONS_SET_TCP(opts, ip_, port_) do { \ (opts)->type = REDICT_CONN_TCP; \ (opts)->endpoint.tcp.ip = ip_; \ (opts)->endpoint.tcp.port = port_; \ } while(0) #define REDICT_OPTIONS_SET_UNIX(opts, path) do { \ (opts)->type = REDICT_CONN_UNIX; \ (opts)->endpoint.unix_socket = path; \ } while(0) #define REDICT_OPTIONS_SET_PRIVDATA(opts, data, dtor) do { \ (opts)->privdata = data; \ (opts)->free_privdata = dtor; \ } while(0) typedef struct redictContextFuncs { void (*close)(struct redictContext *); void (*free_privctx)(void *); void (*async_read)(struct redictAsyncContext *); void (*async_write)(struct redictAsyncContext *); /* Read/Write data to the underlying communication stream, returning the * number of bytes read/written. In the event of an unrecoverable error * these functions shall return a value < 0. In the event of a * recoverable error, they should return 0. */ ssize_t (*read)(struct redictContext *, char *, size_t); ssize_t (*write)(struct redictContext *); } redictContextFuncs; /* Context for a connection to Redict */ typedef struct redictContext { const redictContextFuncs *funcs; /* Function table */ int err; /* Error flags, 0 when there is no error */ char errstr[128]; /* String representation of error when applicable */ redictFD fd; int flags; char *obuf; /* Write buffer */ redictReader *reader; /* Protocol reader */ enum redictConnectionType connection_type; struct timeval *connect_timeout; struct timeval *command_timeout; struct { char *host; char *source_addr; int port; } tcp; struct { char *path; } unix_sock; /* For non-blocking connect */ struct sockaddr *saddr; size_t addrlen; /* Optional data and corresponding destructor users can use to provide * context to a given redictContext. Not used by hiredict. */ void *privdata; void (*free_privdata)(void *); /* Internal context pointer presently used by hiredict to manage * SSL connections. */ void *privctx; /* An optional RESP3 PUSH handler */ redictPushFn *push_cb; } redictContext; redictContext *redictConnectWithOptions(const redictOptions *options); redictContext *redictConnect(const char *ip, int port); redictContext *redictConnectWithTimeout(const char *ip, int port, const struct timeval tv); redictContext *redictConnectNonBlock(const char *ip, int port); redictContext *redictConnectBindNonBlock(const char *ip, int port, const char *source_addr); redictContext *redictConnectBindNonBlockWithReuse(const char *ip, int port, const char *source_addr); redictContext *redictConnectUnix(const char *path); redictContext *redictConnectUnixWithTimeout(const char *path, const struct timeval tv); redictContext *redictConnectUnixNonBlock(const char *path); redictContext *redictConnectFd(redictFD fd); /** * Reconnect the given context using the saved information. * * This re-uses the exact same connect options as in the initial connection. * host, ip (or path), timeout and bind address are reused, * flags are used unmodified from the existing context. * * Returns REDICT_OK on successful connect or REDICT_ERR otherwise. */ int redictReconnect(redictContext *c); redictPushFn *redictSetPushCallback(redictContext *c, redictPushFn *fn); int redictSetTimeout(redictContext *c, const struct timeval tv); int redictEnableKeepAlive(redictContext *c); int redictEnableKeepAliveWithInterval(redictContext *c, int interval); int redictSetTcpUserTimeout(redictContext *c, unsigned int timeout); void redictFree(redictContext *c); redictFD redictFreeKeepFd(redictContext *c); int redictBufferRead(redictContext *c); int redictBufferWrite(redictContext *c, int *done); /* In a blocking context, this function first checks if there are unconsumed * replies to return and returns one if so. Otherwise, it flushes the output * buffer to the socket and reads until it has a reply. In a non-blocking * context, it will return unconsumed replies until there are no more. */ int redictGetReply(redictContext *c, void **reply); int redictGetReplyFromReader(redictContext *c, void **reply); /* Write a formatted command to the output buffer. Use these functions in blocking mode * to get a pipeline of commands. */ int redictAppendFormattedCommand(redictContext *c, const char *cmd, size_t len); /* Write a command to the output buffer. Use these functions in blocking mode * to get a pipeline of commands. */ int redictvAppendCommand(redictContext *c, const char *format, va_list ap); int redictAppendCommand(redictContext *c, const char *format, ...); int redictAppendCommandArgv(redictContext *c, int argc, const char **argv, const size_t *argvlen); /* Issue a command to Redict. In a blocking context, it is identical to calling * redictAppendCommand, followed by redictGetReply. The function will return * NULL if there was an error in performing the request, otherwise it will * return the reply. In a non-blocking context, it is identical to calling * only redictAppendCommand and will always return NULL. */ void *redictvCommand(redictContext *c, const char *format, va_list ap); void *redictCommand(redictContext *c, const char *format, ...); void *redictCommandArgv(redictContext *c, int argc, const char **argv, const size_t *argvlen); #ifdef __cplusplus } #endif #endif hiredict/hiredict.pc.in000066400000000000000000000004611460333451400154010ustar00rootroot00000000000000prefix=@PREFIX@ install_libdir=@LIBDIR@ exec_prefix=${prefix} libdir=${exec_prefix}/${install_libdir} includedir=${prefix}/include Name: hiredict Description: Minimalistic C client library for Redict. Version: @PROJECT_VERSION@ Libs: -L${libdir} -lhiredict Cflags: -I${includedir} -D_FILE_OFFSET_BITS=64 hiredict/hiredict.targets000066400000000000000000000010141460333451400160360ustar00rootroot00000000000000 $(MSBuildThisFileDirectory)\..\..\include;%(AdditionalIncludeDirectories) $(MSBuildThisFileDirectory)\..\..\lib;%(AdditionalLibraryDirectories) hiredict/hiredict_ssl-config.cmake.in000066400000000000000000000006321460333451400202030ustar00rootroot00000000000000@PACKAGE_INIT@ set_and_check(hiredict_ssl_INCLUDEDIR "@PACKAGE_INCLUDE_INSTALL_DIR@") include(CMakeFindDependencyMacro) find_dependency(OpenSSL) IF (NOT TARGET hiredict::hiredict_ssl) INCLUDE(${CMAKE_CURRENT_LIST_DIR}/hiredict_ssl-targets.cmake) ENDIF() SET(hiredict_ssl_LIBRARIES hiredict::hiredict_ssl) SET(hiredict_ssl_INCLUDE_DIRS ${hiredict_ssl_INCLUDEDIR}) check_required_components(hiredict_ssl) hiredict/hiredict_ssl.h000066400000000000000000000112711460333451400155030ustar00rootroot00000000000000/* * Copyright (c) 2019, Redict Labs * * SPDX-FileCopyrightText: 2024 Hiredict Contributors * SPDX-FileCopyrightText: 2024 Redict Labs * * SPDX-License-Identifier: BSD-3-Clause * SPDX-License-Identifier: LGPL-3.0-or-later * */ #ifndef __HIREDICT_SSL_H #define __HIREDICT_SSL_H #ifdef __cplusplus extern "C" { #endif /* This is the underlying struct for SSL in ssl.h, which is not included to * keep build dependencies short here. */ struct ssl_st; /* A wrapper around OpenSSL SSL_CTX to allow easy SSL use without directly * calling OpenSSL. */ typedef struct redictSSLContext redictSSLContext; /** * Initialization errors that redictCreateSSLContext() may return. */ typedef enum { REDICT_SSL_CTX_NONE = 0, /* No Error */ REDICT_SSL_CTX_CREATE_FAILED, /* Failed to create OpenSSL SSL_CTX */ REDICT_SSL_CTX_CERT_KEY_REQUIRED, /* Client cert and key must both be specified or skipped */ REDICT_SSL_CTX_CA_CERT_LOAD_FAILED, /* Failed to load CA Certificate or CA Path */ REDICT_SSL_CTX_CLIENT_CERT_LOAD_FAILED, /* Failed to load client certificate */ REDICT_SSL_CTX_CLIENT_DEFAULT_CERT_FAILED, /* Failed to set client default certificate directory */ REDICT_SSL_CTX_PRIVATE_KEY_LOAD_FAILED, /* Failed to load private key */ REDICT_SSL_CTX_OS_CERTSTORE_OPEN_FAILED, /* Failed to open system certificate store */ REDICT_SSL_CTX_OS_CERT_ADD_FAILED /* Failed to add CA certificates obtained from system to the SSL context */ } redictSSLContextError; /* Constants that mirror OpenSSL's verify modes. By default, * REDICT_SSL_VERIFY_PEER is used with redictCreateSSLContext(). * Some Redict clients disable peer verification if there are no * certificates specified. */ #define REDICT_SSL_VERIFY_NONE 0x00 #define REDICT_SSL_VERIFY_PEER 0x01 #define REDICT_SSL_VERIFY_FAIL_IF_NO_PEER_CERT 0x02 #define REDICT_SSL_VERIFY_CLIENT_ONCE 0x04 #define REDICT_SSL_VERIFY_POST_HANDSHAKE 0x08 /* Options to create an OpenSSL context. */ typedef struct { const char *cacert_filename; const char *capath; const char *cert_filename; const char *private_key_filename; const char *server_name; int verify_mode; } redictSSLOptions; /** * Return the error message corresponding with the specified error code. */ const char *redictSSLContextGetError(redictSSLContextError error); /** * Helper function to initialize the OpenSSL library. * * OpenSSL requires one-time initialization before it can be used. Callers should * call this function only once, and only if OpenSSL is not directly initialized * elsewhere. */ int redictInitOpenSSL(void); /** * Helper function to initialize an OpenSSL context that can be used * to initiate SSL connections. * * cacert_filename is an optional name of a CA certificate/bundle file to load * and use for validation. * * capath is an optional directory path where trusted CA certificate files are * stored in an OpenSSL-compatible structure. * * cert_filename and private_key_filename are optional names of a client side * certificate and private key files to use for authentication. They need to * be both specified or omitted. * * server_name is an optional and will be used as a server name indication * (SNI) TLS extension. * * If error is non-null, it will be populated in case the context creation fails * (returning a NULL). */ redictSSLContext *redictCreateSSLContext(const char *cacert_filename, const char *capath, const char *cert_filename, const char *private_key_filename, const char *server_name, redictSSLContextError *error); /** * Helper function to initialize an OpenSSL context that can be used * to initiate SSL connections. This is a more extensible version of redictCreateSSLContext(). * * options contains a structure of SSL options to use. * * If error is non-null, it will be populated in case the context creation fails * (returning a NULL). */ redictSSLContext *redictCreateSSLContextWithOptions(redictSSLOptions *options, redictSSLContextError *error); /** * Free a previously created OpenSSL context. */ void redictFreeSSLContext(redictSSLContext *redict_ssl_ctx); /** * Initiate SSL on an existing redictContext. * * This is similar to redictInitiateSSL() but does not require the caller * to directly interact with OpenSSL, and instead uses a redictSSLContext * previously created using redictCreateSSLContext(). */ int redictInitiateSSLWithContext(redictContext *c, redictSSLContext *redict_ssl_ctx); /** * Initiate SSL/TLS negotiation on a provided OpenSSL SSL object. */ int redictInitiateSSL(redictContext *c, struct ssl_st *ssl); #ifdef __cplusplus } #endif #endif /* __HIREDICT_SSL_H */ hiredict/hiredict_ssl.pc.in000066400000000000000000000005171460333451400162640ustar00rootroot00000000000000prefix=@PREFIX@ install_libdir=@LIBDIR@ exec_prefix=${prefix} libdir=${exec_prefix}/${install_libdir} includedir=${prefix}/include pkgincludedir=${includedir}/hiredict Name: hiredict_ssl Description: SSL Support for hiredict. Version: @PROJECT_VERSION@ Requires: hiredict Libs: -L${libdir} -lhiredict_ssl Libs.private: -lssl -lcrypto hiredict/lint.sh000077500000000000000000000011771460333451400141670ustar00rootroot00000000000000#!/bin/sh -e # SPDX-FileCopyrightText: 2024 Hiredict Contributors # SPDX-License-Identifier: LGPL-3.0-or-later signoff() { if ! git log --format='%b' HEAD^.. | grep 'Signed-off-by' >/dev/null then echo "Missing sign-off!" >&2 echo "Run 'git commit --amend -s' to fix" >&2 echo "See CONTRIBUTING.md" >&2 return 1 fi } licenses() { if ! chronic sh -c "reuse lint 2>/dev/null" then return 1 fi } exitcode=0 for step in signoff licenses do if ! $step then exitcode=1 fi done if [ $exitcode -eq 0 ] then echo "Everything looks good!" fi exit $exitcode hiredict/net.c000066400000000000000000000503701460333451400136130ustar00rootroot00000000000000/* Extracted from anet.c to work properly with Hiredict error reporting. * * Copyright (c) 2009-2011, Salvatore Sanfilippo * Copyright (c) 2010-2014, Pieter Noordhuis * Copyright (c) 2015, Matt Stancliff , * Jan-Erik Rediger * * SPDX-FileCopyrightText: 2024 Hiredict Contributors * SPDX-FileCopyrightText: 2024 Salvatore Sanfilippo * SPDX-FileCopyrightText: 2024 Pieter Noordhuis * SPDX-FileCopyrightText: 2024 Matt Stancliff * SPDX-FileCopyrightText: 2024 Jan-Erik Rediger * * SPDX-License-Identifier: BSD-3-Clause * SPDX-License-Identifier: LGPL-3.0-or-later * */ #include "fmacros.h" #include #include #include #include #include #include #include #include #include #include "net.h" #include "sds.h" #include "sockcompat.h" #include "win32.h" /* Defined in hiredict.c */ void __redictSetError(redictContext *c, int type, const char *str); int redictContextUpdateCommandTimeout(redictContext *c, const struct timeval *timeout); void redictNetClose(redictContext *c) { if (c && c->fd != REDICT_INVALID_FD) { close(c->fd); c->fd = REDICT_INVALID_FD; } } ssize_t redictNetRead(redictContext *c, char *buf, size_t bufcap) { ssize_t nread = recv(c->fd, buf, bufcap, 0); if (nread == -1) { if ((errno == EWOULDBLOCK && !(c->flags & REDICT_BLOCK)) || (errno == EINTR)) { /* Try again later */ return 0; } else if(errno == ETIMEDOUT && (c->flags & REDICT_BLOCK)) { /* especially in windows */ __redictSetError(c, REDICT_ERR_TIMEOUT, "recv timeout"); return -1; } else { __redictSetError(c, REDICT_ERR_IO, strerror(errno)); return -1; } } else if (nread == 0) { __redictSetError(c, REDICT_ERR_EOF, "Server closed the connection"); return -1; } else { return nread; } } ssize_t redictNetWrite(redictContext *c) { ssize_t nwritten; nwritten = send(c->fd, c->obuf, sdslen(c->obuf), 0); if (nwritten < 0) { if ((errno == EWOULDBLOCK && !(c->flags & REDICT_BLOCK)) || (errno == EINTR)) { /* Try again */ return 0; } else { __redictSetError(c, REDICT_ERR_IO, strerror(errno)); return -1; } } return nwritten; } static void __redictSetErrorFromErrno(redictContext *c, int type, const char *prefix) { int errorno = errno; /* snprintf() may change errno */ char buf[128] = { 0 }; size_t len = 0; if (prefix != NULL) len = snprintf(buf,sizeof(buf),"%s: ",prefix); strerror_r(errorno, (char *)(buf + len), sizeof(buf) - len); __redictSetError(c,type,buf); } static int redictSetReuseAddr(redictContext *c) { int on = 1; if (setsockopt(c->fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) { __redictSetErrorFromErrno(c,REDICT_ERR_IO,NULL); redictNetClose(c); return REDICT_ERR; } return REDICT_OK; } static int redictCreateSocket(redictContext *c, int type) { redictFD s; if ((s = socket(type, SOCK_STREAM, 0)) == REDICT_INVALID_FD) { __redictSetErrorFromErrno(c,REDICT_ERR_IO,NULL); return REDICT_ERR; } c->fd = s; if (type == AF_INET) { if (redictSetReuseAddr(c) == REDICT_ERR) { return REDICT_ERR; } } return REDICT_OK; } static int redictSetBlocking(redictContext *c, int blocking) { #ifndef _WIN32 int flags; /* Set the socket nonblocking. * Note that fcntl(2) for F_GETFL and F_SETFL can't be * interrupted by a signal. */ if ((flags = fcntl(c->fd, F_GETFL)) == -1) { __redictSetErrorFromErrno(c,REDICT_ERR_IO,"fcntl(F_GETFL)"); redictNetClose(c); return REDICT_ERR; } if (blocking) flags &= ~O_NONBLOCK; else flags |= O_NONBLOCK; if (fcntl(c->fd, F_SETFL, flags) == -1) { __redictSetErrorFromErrno(c,REDICT_ERR_IO,"fcntl(F_SETFL)"); redictNetClose(c); return REDICT_ERR; } #else u_long mode = blocking ? 0 : 1; if (ioctl(c->fd, FIONBIO, &mode) == -1) { __redictSetErrorFromErrno(c, REDICT_ERR_IO, "ioctl(FIONBIO)"); redictNetClose(c); return REDICT_ERR; } #endif /* _WIN32 */ return REDICT_OK; } int redictKeepAlive(redictContext *c, int interval) { int val = 1; redictFD fd = c->fd; /* TCP_KEEPALIVE makes no sense with AF_UNIX connections */ if (c->connection_type == REDICT_CONN_UNIX) return REDICT_ERR; #ifndef _WIN32 if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &val, sizeof(val)) == -1){ __redictSetError(c,REDICT_ERR_OTHER,strerror(errno)); return REDICT_ERR; } val = interval; #if defined(__APPLE__) && defined(__MACH__) if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPALIVE, &val, sizeof(val)) < 0) { __redictSetError(c,REDICT_ERR_OTHER,strerror(errno)); return REDICT_ERR; } #else #if defined(__GLIBC__) && !defined(__FreeBSD_kernel__) if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE, &val, sizeof(val)) < 0) { __redictSetError(c,REDICT_ERR_OTHER,strerror(errno)); return REDICT_ERR; } val = interval/3; if (val == 0) val = 1; if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPINTVL, &val, sizeof(val)) < 0) { __redictSetError(c,REDICT_ERR_OTHER,strerror(errno)); return REDICT_ERR; } val = 3; if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPCNT, &val, sizeof(val)) < 0) { __redictSetError(c,REDICT_ERR_OTHER,strerror(errno)); return REDICT_ERR; } #endif #endif #else int res; res = win32_redictKeepAlive(fd, interval * 1000); if (res != 0) { __redictSetError(c, REDICT_ERR_OTHER, strerror(res)); return REDICT_ERR; } #endif return REDICT_OK; } int redictSetTcpNoDelay(redictContext *c) { int yes = 1; if (setsockopt(c->fd, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof(yes)) == -1) { __redictSetErrorFromErrno(c,REDICT_ERR_IO,"setsockopt(TCP_NODELAY)"); redictNetClose(c); return REDICT_ERR; } return REDICT_OK; } int redictContextSetTcpUserTimeout(redictContext *c, unsigned int timeout) { int res; #ifdef TCP_USER_TIMEOUT res = setsockopt(c->fd, IPPROTO_TCP, TCP_USER_TIMEOUT, &timeout, sizeof(timeout)); #else res = -1; errno = ENOTSUP; (void)timeout; #endif if (res == -1) { __redictSetErrorFromErrno(c,REDICT_ERR_IO,"setsockopt(TCP_USER_TIMEOUT)"); redictNetClose(c); return REDICT_ERR; } return REDICT_OK; } #define __MAX_MSEC (((LONG_MAX) - 999) / 1000) static int redictContextTimeoutMsec(redictContext *c, long *result) { const struct timeval *timeout = c->connect_timeout; long msec = -1; /* Only use timeout when not NULL. */ if (timeout != NULL) { if (timeout->tv_usec > 1000000 || timeout->tv_sec > __MAX_MSEC) { __redictSetError(c, REDICT_ERR_IO, "Invalid timeout specified"); *result = msec; return REDICT_ERR; } msec = (timeout->tv_sec * 1000) + ((timeout->tv_usec + 999) / 1000); if (msec < 0 || msec > INT_MAX) { msec = INT_MAX; } } *result = msec; return REDICT_OK; } static long redictPollMillis(void) { #ifndef _MSC_VER struct timespec now; clock_gettime(CLOCK_MONOTONIC, &now); return (now.tv_sec * 1000) + now.tv_nsec / 1000000; #else FILETIME ft; GetSystemTimeAsFileTime(&ft); return (((long long)ft.dwHighDateTime << 32) | ft.dwLowDateTime) / 10; #endif } static int redictContextWaitReady(redictContext *c, long msec) { struct pollfd wfd; long end; int res; if (errno != EINPROGRESS) { __redictSetErrorFromErrno(c,REDICT_ERR_IO,NULL); redictNetClose(c); return REDICT_ERR; } wfd.fd = c->fd; wfd.events = POLLOUT; end = msec >= 0 ? redictPollMillis() + msec : 0; while ((res = poll(&wfd, 1, msec)) <= 0) { if (res < 0 && errno != EINTR) { __redictSetErrorFromErrno(c, REDICT_ERR_IO, "poll(2)"); redictNetClose(c); return REDICT_ERR; } else if (res == 0 || (msec >= 0 && redictPollMillis() >= end)) { errno = ETIMEDOUT; __redictSetErrorFromErrno(c, REDICT_ERR_IO, NULL); redictNetClose(c); return REDICT_ERR; } else { /* res < 0 && errno == EINTR, try again */ } } if (redictCheckConnectDone(c, &res) != REDICT_OK || res == 0) { redictCheckSocketError(c); return REDICT_ERR; } return REDICT_OK; } int redictCheckConnectDone(redictContext *c, int *completed) { int rc = connect(c->fd, (const struct sockaddr *)c->saddr, c->addrlen); if (rc == 0) { *completed = 1; return REDICT_OK; } int error = errno; if (error == EINPROGRESS) { /* must check error to see if connect failed. Get the socket error */ int fail, so_error; socklen_t optlen = sizeof(so_error); fail = getsockopt(c->fd, SOL_SOCKET, SO_ERROR, &so_error, &optlen); if (fail == 0) { if (so_error == 0) { /* Socket is connected! */ *completed = 1; return REDICT_OK; } /* connection error; */ errno = so_error; error = so_error; } } switch (error) { case EISCONN: *completed = 1; return REDICT_OK; case EALREADY: case EWOULDBLOCK: *completed = 0; return REDICT_OK; default: return REDICT_ERR; } } int redictCheckSocketError(redictContext *c) { int err = 0, errno_saved = errno; socklen_t errlen = sizeof(err); if (getsockopt(c->fd, SOL_SOCKET, SO_ERROR, &err, &errlen) == -1) { __redictSetErrorFromErrno(c,REDICT_ERR_IO,"getsockopt(SO_ERROR)"); return REDICT_ERR; } if (err == 0) { err = errno_saved; } if (err) { errno = err; __redictSetErrorFromErrno(c,REDICT_ERR_IO,NULL); return REDICT_ERR; } return REDICT_OK; } int redictContextSetTimeout(redictContext *c, const struct timeval tv) { const void *to_ptr = &tv; size_t to_sz = sizeof(tv); if (redictContextUpdateCommandTimeout(c, &tv) != REDICT_OK) { __redictSetError(c, REDICT_ERR_OOM, "Out of memory"); return REDICT_ERR; } if (setsockopt(c->fd,SOL_SOCKET,SO_RCVTIMEO,to_ptr,to_sz) == -1) { __redictSetErrorFromErrno(c,REDICT_ERR_IO,"setsockopt(SO_RCVTIMEO)"); return REDICT_ERR; } if (setsockopt(c->fd,SOL_SOCKET,SO_SNDTIMEO,to_ptr,to_sz) == -1) { __redictSetErrorFromErrno(c,REDICT_ERR_IO,"setsockopt(SO_SNDTIMEO)"); return REDICT_ERR; } return REDICT_OK; } int redictContextUpdateConnectTimeout(redictContext *c, const struct timeval *timeout) { /* Same timeval struct, short circuit */ if (c->connect_timeout == timeout) return REDICT_OK; /* Allocate context timeval if we need to */ if (c->connect_timeout == NULL) { c->connect_timeout = hi_malloc(sizeof(*c->connect_timeout)); if (c->connect_timeout == NULL) return REDICT_ERR; } memcpy(c->connect_timeout, timeout, sizeof(*c->connect_timeout)); return REDICT_OK; } int redictContextUpdateCommandTimeout(redictContext *c, const struct timeval *timeout) { /* Same timeval struct, short circuit */ if (c->command_timeout == timeout) return REDICT_OK; /* Allocate context timeval if we need to */ if (c->command_timeout == NULL) { c->command_timeout = hi_malloc(sizeof(*c->command_timeout)); if (c->command_timeout == NULL) return REDICT_ERR; } memcpy(c->command_timeout, timeout, sizeof(*c->command_timeout)); return REDICT_OK; } static int _redictContextConnectTcp(redictContext *c, const char *addr, int port, const struct timeval *timeout, const char *source_addr) { redictFD s; int rv, n; char _port[6]; /* strlen("65535"); */ struct addrinfo hints, *servinfo, *bservinfo, *p, *b; int blocking = (c->flags & REDICT_BLOCK); int reuseaddr = (c->flags & REDICT_REUSEADDR); int reuses = 0; long timeout_msec = -1; servinfo = NULL; c->connection_type = REDICT_CONN_TCP; c->tcp.port = port; /* We need to take possession of the passed parameters * to make them reusable for a reconnect. * We also carefully check we don't free data we already own, * as in the case of the reconnect method. * * This is a bit ugly, but atleast it works and doesn't leak memory. **/ if (c->tcp.host != addr) { hi_free(c->tcp.host); c->tcp.host = hi_strdup(addr); if (c->tcp.host == NULL) goto oom; } if (timeout) { if (redictContextUpdateConnectTimeout(c, timeout) == REDICT_ERR) goto oom; } else { hi_free(c->connect_timeout); c->connect_timeout = NULL; } if (redictContextTimeoutMsec(c, &timeout_msec) != REDICT_OK) { goto error; } if (source_addr == NULL) { hi_free(c->tcp.source_addr); c->tcp.source_addr = NULL; } else if (c->tcp.source_addr != source_addr) { hi_free(c->tcp.source_addr); c->tcp.source_addr = hi_strdup(source_addr); } snprintf(_port, 6, "%d", port); memset(&hints,0,sizeof(hints)); hints.ai_family = AF_INET; hints.ai_socktype = SOCK_STREAM; /* DNS lookup. To use dual stack, set both flags to prefer both IPv4 and * IPv6. By default, for historical reasons, we try IPv4 first and then we * try IPv6 only if no IPv4 address was found. */ if (c->flags & REDICT_PREFER_IPV6 && c->flags & REDICT_PREFER_IPV4) hints.ai_family = AF_UNSPEC; else if (c->flags & REDICT_PREFER_IPV6) hints.ai_family = AF_INET6; else hints.ai_family = AF_INET; rv = getaddrinfo(c->tcp.host, _port, &hints, &servinfo); if (rv != 0 && hints.ai_family != AF_UNSPEC) { /* Try again with the other IP version. */ hints.ai_family = (hints.ai_family == AF_INET) ? AF_INET6 : AF_INET; rv = getaddrinfo(c->tcp.host, _port, &hints, &servinfo); } if (rv != 0) { __redictSetError(c, REDICT_ERR_OTHER, gai_strerror(rv)); return REDICT_ERR; } for (p = servinfo; p != NULL; p = p->ai_next) { addrretry: if ((s = socket(p->ai_family,p->ai_socktype,p->ai_protocol)) == REDICT_INVALID_FD) continue; c->fd = s; if (redictSetBlocking(c,0) != REDICT_OK) goto error; if (c->tcp.source_addr) { int bound = 0; /* Using getaddrinfo saves us from self-determining IPv4 vs IPv6 */ if ((rv = getaddrinfo(c->tcp.source_addr, NULL, &hints, &bservinfo)) != 0) { char buf[128]; snprintf(buf,sizeof(buf),"Can't get addr: %s",gai_strerror(rv)); __redictSetError(c,REDICT_ERR_OTHER,buf); goto error; } if (reuseaddr) { n = 1; if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char*) &n, sizeof(n)) < 0) { freeaddrinfo(bservinfo); goto error; } } for (b = bservinfo; b != NULL; b = b->ai_next) { if (bind(s,b->ai_addr,b->ai_addrlen) != -1) { bound = 1; break; } } freeaddrinfo(bservinfo); if (!bound) { char buf[128]; snprintf(buf,sizeof(buf),"Can't bind socket: %s",strerror(errno)); __redictSetError(c,REDICT_ERR_OTHER,buf); goto error; } } /* For repeat connection */ hi_free(c->saddr); c->saddr = hi_malloc(p->ai_addrlen); if (c->saddr == NULL) goto oom; memcpy(c->saddr, p->ai_addr, p->ai_addrlen); c->addrlen = p->ai_addrlen; if (connect(s,p->ai_addr,p->ai_addrlen) == -1) { if (errno == EHOSTUNREACH) { redictNetClose(c); continue; } else if (errno == EINPROGRESS) { if (blocking) { goto wait_for_ready; } /* This is ok. * Note that even when it's in blocking mode, we unset blocking * for `connect()` */ } else if (errno == EADDRNOTAVAIL && reuseaddr) { if (++reuses >= REDICT_CONNECT_RETRIES) { goto error; } else { redictNetClose(c); goto addrretry; } } else { wait_for_ready: if (redictContextWaitReady(c,timeout_msec) != REDICT_OK) goto error; if (redictSetTcpNoDelay(c) != REDICT_OK) goto error; } } if (blocking && redictSetBlocking(c,1) != REDICT_OK) goto error; c->flags |= REDICT_CONNECTED; rv = REDICT_OK; goto end; } if (p == NULL) { char buf[128]; snprintf(buf,sizeof(buf),"Can't create socket: %s",strerror(errno)); __redictSetError(c,REDICT_ERR_OTHER,buf); goto error; } oom: __redictSetError(c, REDICT_ERR_OOM, "Out of memory"); error: rv = REDICT_ERR; end: if(servinfo) { freeaddrinfo(servinfo); } return rv; // Need to return REDICT_OK if alright } int redictContextConnectTcp(redictContext *c, const char *addr, int port, const struct timeval *timeout) { return _redictContextConnectTcp(c, addr, port, timeout, NULL); } int redictContextConnectBindTcp(redictContext *c, const char *addr, int port, const struct timeval *timeout, const char *source_addr) { return _redictContextConnectTcp(c, addr, port, timeout, source_addr); } int redictContextConnectUnix(redictContext *c, const char *path, const struct timeval *timeout) { #ifndef _WIN32 int blocking = (c->flags & REDICT_BLOCK); struct sockaddr_un *sa; long timeout_msec = -1; if (redictCreateSocket(c,AF_UNIX) < 0) return REDICT_ERR; if (redictSetBlocking(c,0) != REDICT_OK) return REDICT_ERR; c->connection_type = REDICT_CONN_UNIX; if (c->unix_sock.path != path) { hi_free(c->unix_sock.path); c->unix_sock.path = hi_strdup(path); if (c->unix_sock.path == NULL) goto oom; } if (timeout) { if (redictContextUpdateConnectTimeout(c, timeout) == REDICT_ERR) goto oom; } else { hi_free(c->connect_timeout); c->connect_timeout = NULL; } if (redictContextTimeoutMsec(c,&timeout_msec) != REDICT_OK) return REDICT_ERR; /* Don't leak sockaddr if we're reconnecting */ if (c->saddr) hi_free(c->saddr); sa = (struct sockaddr_un*)(c->saddr = hi_malloc(sizeof(struct sockaddr_un))); if (sa == NULL) goto oom; c->addrlen = sizeof(struct sockaddr_un); sa->sun_family = AF_UNIX; strncpy(sa->sun_path, path, sizeof(sa->sun_path) - 1); if (connect(c->fd, (struct sockaddr*)sa, sizeof(*sa)) == -1) { if (errno == EINPROGRESS && !blocking) { /* This is ok. */ } else { if (redictContextWaitReady(c,timeout_msec) != REDICT_OK) return REDICT_ERR; } } /* Reset socket to be blocking after connect(2). */ if (blocking && redictSetBlocking(c,1) != REDICT_OK) return REDICT_ERR; c->flags |= REDICT_CONNECTED; return REDICT_OK; #else /* We currently do not support Unix sockets for Windows. */ /* TODO(m): https://devblogs.microsoft.com/commandline/af_unix-comes-to-windows/ */ errno = EPROTONOSUPPORT; return REDICT_ERR; #endif /* _WIN32 */ oom: __redictSetError(c, REDICT_ERR_OOM, "Out of memory"); return REDICT_ERR; } hiredict/net.h000066400000000000000000000034001460333451400136100ustar00rootroot00000000000000/* Extracted from anet.c to work properly with Hiredict error reporting. * * Copyright (c) 2009-2011, Salvatore Sanfilippo * Copyright (c) 2010-2014, Pieter Noordhuis * Copyright (c) 2015, Matt Stancliff , * Jan-Erik Rediger * * SPDX-FileCopyrightText: 2024 Hiredict Contributors * SPDX-FileCopyrightText: 2024 Salvatore Sanfilippo * SPDX-FileCopyrightText: 2024 Pieter Noordhuis * SPDX-FileCopyrightText: 2024 Matt Stancliff * SPDX-FileCopyrightText: 2024 Jan-Erik Rediger * * SPDX-License-Identifier: BSD-3-Clause * SPDX-License-Identifier: LGPL-3.0-or-later * */ #ifndef __NET_H #define __NET_H #include "hiredict.h" void redictNetClose(redictContext *c); ssize_t redictNetRead(redictContext *c, char *buf, size_t bufcap); ssize_t redictNetWrite(redictContext *c); int redictCheckSocketError(redictContext *c); int redictContextSetTimeout(redictContext *c, const struct timeval tv); int redictContextConnectTcp(redictContext *c, const char *addr, int port, const struct timeval *timeout); int redictContextConnectBindTcp(redictContext *c, const char *addr, int port, const struct timeval *timeout, const char *source_addr); int redictContextConnectUnix(redictContext *c, const char *path, const struct timeval *timeout); int redictKeepAlive(redictContext *c, int interval); int redictCheckConnectDone(redictContext *c, int *completed); int redictSetTcpNoDelay(redictContext *c); int redictContextSetTcpUserTimeout(redictContext *c, unsigned int timeout); #endif hiredict/read.c000066400000000000000000000541361460333451400137440ustar00rootroot00000000000000/* * Copyright (c) 2009-2011, Salvatore Sanfilippo * Copyright (c) 2010-2011, Pieter Noordhuis * * SPDX-FileCopyrightText: 2024 Hiredict Contributors * SPDX-FileCopyrightText: 2024 Salvatore Sanfilippo * SPDX-FileCopyrightText: 2024 Pieter Noordhuis * * SPDX-License-Identifier: BSD-3-Clause * SPDX-License-Identifier: LGPL-3.0-or-later * */ #include "fmacros.h" #include #include #ifndef _MSC_VER #include #include #endif #include #include #include #include #include #include "alloc.h" #include "read.h" #include "sds.h" #include "win32.h" /* Initial size of our nested reply stack and how much we grow it when needd */ #define REDICT_READER_STACK_SIZE 9 static void __redictReaderSetError(redictReader *r, int type, const char *str) { size_t len; if (r->reply != NULL && r->fn && r->fn->freeObject) { r->fn->freeObject(r->reply); r->reply = NULL; } /* Clear input buffer on errors. */ sdsfree(r->buf); r->buf = NULL; r->pos = r->len = 0; /* Reset task stack. */ r->ridx = -1; /* Set error. */ r->err = type; len = strlen(str); len = len < (sizeof(r->errstr)-1) ? len : (sizeof(r->errstr)-1); memcpy(r->errstr,str,len); r->errstr[len] = '\0'; } static size_t chrtos(char *buf, size_t size, char byte) { size_t len = 0; switch(byte) { case '\\': case '"': len = snprintf(buf,size,"\"\\%c\"",byte); break; case '\n': len = snprintf(buf,size,"\"\\n\""); break; case '\r': len = snprintf(buf,size,"\"\\r\""); break; case '\t': len = snprintf(buf,size,"\"\\t\""); break; case '\a': len = snprintf(buf,size,"\"\\a\""); break; case '\b': len = snprintf(buf,size,"\"\\b\""); break; default: if (isprint(byte)) len = snprintf(buf,size,"\"%c\"",byte); else len = snprintf(buf,size,"\"\\x%02x\"",(unsigned char)byte); break; } return len; } static void __redictReaderSetErrorProtocolByte(redictReader *r, char byte) { char cbuf[8], sbuf[128]; chrtos(cbuf,sizeof(cbuf),byte); snprintf(sbuf,sizeof(sbuf), "Protocol error, got %s as reply type byte", cbuf); __redictReaderSetError(r,REDICT_ERR_PROTOCOL,sbuf); } static void __redictReaderSetErrorOOM(redictReader *r) { __redictReaderSetError(r,REDICT_ERR_OOM,"Out of memory"); } static char *readBytes(redictReader *r, unsigned int bytes) { char *p; if (r->len-r->pos >= bytes) { p = r->buf+r->pos; r->pos += bytes; return p; } return NULL; } /* Find pointer to \r\n. */ static char *seekNewline(char *s, size_t len) { char *ret; /* We cannot match with fewer than 2 bytes */ if (len < 2) return NULL; /* Search up to len - 1 characters */ len--; /* Look for the \r */ while ((ret = memchr(s, '\r', len)) != NULL) { if (ret[1] == '\n') { /* Found. */ break; } /* Continue searching. */ ret++; len -= ret - s; s = ret; } return ret; } /* Convert a string into a long long. Returns REDICT_OK if the string could be * parsed into a (non-overflowing) long long, REDICT_ERR otherwise. The value * will be set to the parsed value when appropriate. * * Note that this function demands that the string strictly represents * a long long: no spaces or other characters before or after the string * representing the number are accepted, nor zeroes at the start if not * for the string "0" representing the zero number. * * Because of its strictness, it is safe to use this function to check if * you can convert a string into a long long, and obtain back the string * from the number without any loss in the string representation. */ static int string2ll(const char *s, size_t slen, long long *value) { const char *p = s; size_t plen = 0; int negative = 0; unsigned long long v; if (plen == slen) return REDICT_ERR; /* Special case: first and only digit is 0. */ if (slen == 1 && p[0] == '0') { if (value != NULL) *value = 0; return REDICT_OK; } if (p[0] == '-') { negative = 1; p++; plen++; /* Abort on only a negative sign. */ if (plen == slen) return REDICT_ERR; } /* First digit should be 1-9, otherwise the string should just be 0. */ if (p[0] >= '1' && p[0] <= '9') { v = p[0]-'0'; p++; plen++; } else if (p[0] == '0' && slen == 1) { *value = 0; return REDICT_OK; } else { return REDICT_ERR; } while (plen < slen && p[0] >= '0' && p[0] <= '9') { if (v > (ULLONG_MAX / 10)) /* Overflow. */ return REDICT_ERR; v *= 10; if (v > (ULLONG_MAX - (p[0]-'0'))) /* Overflow. */ return REDICT_ERR; v += p[0]-'0'; p++; plen++; } /* Return if not all bytes were used. */ if (plen < slen) return REDICT_ERR; if (negative) { if (v > ((unsigned long long)(-(LLONG_MIN+1))+1)) /* Overflow. */ return REDICT_ERR; if (value != NULL) *value = -v; } else { if (v > LLONG_MAX) /* Overflow. */ return REDICT_ERR; if (value != NULL) *value = v; } return REDICT_OK; } static char *readLine(redictReader *r, int *_len) { char *p, *s; int len; p = r->buf+r->pos; s = seekNewline(p,(r->len-r->pos)); if (s != NULL) { len = s-(r->buf+r->pos); r->pos += len+2; /* skip \r\n */ if (_len) *_len = len; return p; } return NULL; } static void moveToNextTask(redictReader *r) { redictReadTask *cur, *prv; while (r->ridx >= 0) { /* Return a.s.a.p. when the stack is now empty. */ if (r->ridx == 0) { r->ridx--; return; } cur = r->task[r->ridx]; prv = r->task[r->ridx-1]; assert(prv->type == REDICT_REPLY_ARRAY || prv->type == REDICT_REPLY_MAP || prv->type == REDICT_REPLY_ATTR || prv->type == REDICT_REPLY_SET || prv->type == REDICT_REPLY_PUSH); if (cur->idx == prv->elements-1) { r->ridx--; } else { /* Reset the type because the next item can be anything */ assert(cur->idx < prv->elements); cur->type = -1; cur->elements = -1; cur->idx++; return; } } } static int processLineItem(redictReader *r) { redictReadTask *cur = r->task[r->ridx]; void *obj; char *p; int len; if ((p = readLine(r,&len)) != NULL) { if (cur->type == REDICT_REPLY_INTEGER) { long long v; if (string2ll(p, len, &v) == REDICT_ERR) { __redictReaderSetError(r,REDICT_ERR_PROTOCOL, "Bad integer value"); return REDICT_ERR; } if (r->fn && r->fn->createInteger) { obj = r->fn->createInteger(cur,v); } else { obj = (void*)REDICT_REPLY_INTEGER; } } else if (cur->type == REDICT_REPLY_DOUBLE) { char buf[326], *eptr; double d; if ((size_t)len >= sizeof(buf)) { __redictReaderSetError(r,REDICT_ERR_PROTOCOL, "Double value is too large"); return REDICT_ERR; } memcpy(buf,p,len); buf[len] = '\0'; if (len == 3 && strcasecmp(buf,"inf") == 0) { d = INFINITY; /* Positive infinite. */ } else if (len == 4 && strcasecmp(buf,"-inf") == 0) { d = -INFINITY; /* Negative infinite. */ } else if ((len == 3 && strcasecmp(buf,"nan") == 0) || (len == 4 && strcasecmp(buf, "-nan") == 0)) { d = NAN; /* nan. */ } else { d = strtod((char*)buf,&eptr); /* RESP3 only allows "inf", "-inf", and finite values, while * strtod() allows other variations on infinity, * etc. We explicity handle our two allowed infinite cases and NaN * above, so strtod() should only result in finite values. */ if (buf[0] == '\0' || eptr != &buf[len] || !isfinite(d)) { __redictReaderSetError(r,REDICT_ERR_PROTOCOL, "Bad double value"); return REDICT_ERR; } } if (r->fn && r->fn->createDouble) { obj = r->fn->createDouble(cur,d,buf,len); } else { obj = (void*)REDICT_REPLY_DOUBLE; } } else if (cur->type == REDICT_REPLY_NIL) { if (len != 0) { __redictReaderSetError(r,REDICT_ERR_PROTOCOL, "Bad nil value"); return REDICT_ERR; } if (r->fn && r->fn->createNil) obj = r->fn->createNil(cur); else obj = (void*)REDICT_REPLY_NIL; } else if (cur->type == REDICT_REPLY_BOOL) { int bval; if (len != 1 || !strchr("tTfF", p[0])) { __redictReaderSetError(r,REDICT_ERR_PROTOCOL, "Bad bool value"); return REDICT_ERR; } bval = p[0] == 't' || p[0] == 'T'; if (r->fn && r->fn->createBool) obj = r->fn->createBool(cur,bval); else obj = (void*)REDICT_REPLY_BOOL; } else if (cur->type == REDICT_REPLY_BIGNUM) { /* Ensure all characters are decimal digits (with possible leading * minus sign). */ for (int i = 0; i < len; i++) { /* XXX Consider: Allow leading '+'? Error on leading '0's? */ if (i == 0 && p[0] == '-') continue; if (p[i] < '0' || p[i] > '9') { __redictReaderSetError(r,REDICT_ERR_PROTOCOL, "Bad bignum value"); return REDICT_ERR; } } if (r->fn && r->fn->createString) obj = r->fn->createString(cur,p,len); else obj = (void*)REDICT_REPLY_BIGNUM; } else { /* Type will be error or status. */ for (int i = 0; i < len; i++) { if (p[i] == '\r' || p[i] == '\n') { __redictReaderSetError(r,REDICT_ERR_PROTOCOL, "Bad simple string value"); return REDICT_ERR; } } if (r->fn && r->fn->createString) obj = r->fn->createString(cur,p,len); else obj = (void*)(uintptr_t)(cur->type); } if (obj == NULL) { __redictReaderSetErrorOOM(r); return REDICT_ERR; } /* Set reply if this is the root object. */ if (r->ridx == 0) r->reply = obj; moveToNextTask(r); return REDICT_OK; } return REDICT_ERR; } static int processBulkItem(redictReader *r) { redictReadTask *cur = r->task[r->ridx]; void *obj = NULL; char *p, *s; long long len; unsigned long bytelen; int success = 0; p = r->buf+r->pos; s = seekNewline(p,r->len-r->pos); if (s != NULL) { p = r->buf+r->pos; bytelen = s-(r->buf+r->pos)+2; /* include \r\n */ if (string2ll(p, bytelen - 2, &len) == REDICT_ERR) { __redictReaderSetError(r,REDICT_ERR_PROTOCOL, "Bad bulk string length"); return REDICT_ERR; } if (len < -1 || (LLONG_MAX > SIZE_MAX && len > (long long)SIZE_MAX)) { __redictReaderSetError(r,REDICT_ERR_PROTOCOL, "Bulk string length out of range"); return REDICT_ERR; } if (len == -1) { /* The nil object can always be created. */ if (r->fn && r->fn->createNil) obj = r->fn->createNil(cur); else obj = (void*)REDICT_REPLY_NIL; success = 1; } else { /* Only continue when the buffer contains the entire bulk item. */ bytelen += len+2; /* include \r\n */ if (r->pos+bytelen <= r->len) { if ((cur->type == REDICT_REPLY_VERB && len < 4) || (cur->type == REDICT_REPLY_VERB && s[5] != ':')) { __redictReaderSetError(r,REDICT_ERR_PROTOCOL, "Verbatim string 4 bytes of content type are " "missing or incorrectly encoded."); return REDICT_ERR; } if (r->fn && r->fn->createString) obj = r->fn->createString(cur,s+2,len); else obj = (void*)(uintptr_t)cur->type; success = 1; } } /* Proceed when obj was created. */ if (success) { if (obj == NULL) { __redictReaderSetErrorOOM(r); return REDICT_ERR; } r->pos += bytelen; /* Set reply if this is the root object. */ if (r->ridx == 0) r->reply = obj; moveToNextTask(r); return REDICT_OK; } } return REDICT_ERR; } static int redictReaderGrow(redictReader *r) { redictReadTask **aux; int newlen; /* Grow our stack size */ newlen = r->tasks + REDICT_READER_STACK_SIZE; aux = hi_realloc(r->task, sizeof(*r->task) * newlen); if (aux == NULL) goto oom; r->task = aux; /* Allocate new tasks */ for (; r->tasks < newlen; r->tasks++) { r->task[r->tasks] = hi_calloc(1, sizeof(**r->task)); if (r->task[r->tasks] == NULL) goto oom; } return REDICT_OK; oom: __redictReaderSetErrorOOM(r); return REDICT_ERR; } /* Process the array, map and set types. */ static int processAggregateItem(redictReader *r) { redictReadTask *cur = r->task[r->ridx]; void *obj; char *p; long long elements; int root = 0, len; if (r->ridx == r->tasks - 1) { if (redictReaderGrow(r) == REDICT_ERR) return REDICT_ERR; } if ((p = readLine(r,&len)) != NULL) { if (string2ll(p, len, &elements) == REDICT_ERR) { __redictReaderSetError(r,REDICT_ERR_PROTOCOL, "Bad multi-bulk length"); return REDICT_ERR; } root = (r->ridx == 0); if (elements < -1 || (LLONG_MAX > SIZE_MAX && elements > SIZE_MAX) || (r->maxelements > 0 && elements > r->maxelements)) { __redictReaderSetError(r,REDICT_ERR_PROTOCOL, "Multi-bulk length out of range"); return REDICT_ERR; } if (elements == -1) { if (r->fn && r->fn->createNil) obj = r->fn->createNil(cur); else obj = (void*)REDICT_REPLY_NIL; if (obj == NULL) { __redictReaderSetErrorOOM(r); return REDICT_ERR; } moveToNextTask(r); } else { if (cur->type == REDICT_REPLY_MAP || cur->type == REDICT_REPLY_ATTR) elements *= 2; if (r->fn && r->fn->createArray) obj = r->fn->createArray(cur,elements); else obj = (void*)(uintptr_t)cur->type; if (obj == NULL) { __redictReaderSetErrorOOM(r); return REDICT_ERR; } /* Modify task stack when there are more than 0 elements. */ if (elements > 0) { cur->elements = elements; cur->obj = obj; r->ridx++; r->task[r->ridx]->type = -1; r->task[r->ridx]->elements = -1; r->task[r->ridx]->idx = 0; r->task[r->ridx]->obj = NULL; r->task[r->ridx]->parent = cur; r->task[r->ridx]->privdata = r->privdata; } else { moveToNextTask(r); } } /* Set reply if this is the root object. */ if (root) r->reply = obj; return REDICT_OK; } return REDICT_ERR; } static int processItem(redictReader *r) { redictReadTask *cur = r->task[r->ridx]; char *p; /* check if we need to read type */ if (cur->type < 0) { if ((p = readBytes(r,1)) != NULL) { switch (p[0]) { case '-': cur->type = REDICT_REPLY_ERROR; break; case '+': cur->type = REDICT_REPLY_STATUS; break; case ':': cur->type = REDICT_REPLY_INTEGER; break; case ',': cur->type = REDICT_REPLY_DOUBLE; break; case '_': cur->type = REDICT_REPLY_NIL; break; case '$': cur->type = REDICT_REPLY_STRING; break; case '*': cur->type = REDICT_REPLY_ARRAY; break; case '%': cur->type = REDICT_REPLY_MAP; break; case '|': cur->type = REDICT_REPLY_ATTR; break; case '~': cur->type = REDICT_REPLY_SET; break; case '#': cur->type = REDICT_REPLY_BOOL; break; case '=': cur->type = REDICT_REPLY_VERB; break; case '>': cur->type = REDICT_REPLY_PUSH; break; case '(': cur->type = REDICT_REPLY_BIGNUM; break; default: __redictReaderSetErrorProtocolByte(r,*p); return REDICT_ERR; } } else { /* could not consume 1 byte */ return REDICT_ERR; } } /* process typed item */ switch(cur->type) { case REDICT_REPLY_ERROR: case REDICT_REPLY_STATUS: case REDICT_REPLY_INTEGER: case REDICT_REPLY_DOUBLE: case REDICT_REPLY_NIL: case REDICT_REPLY_BOOL: case REDICT_REPLY_BIGNUM: return processLineItem(r); case REDICT_REPLY_STRING: case REDICT_REPLY_VERB: return processBulkItem(r); case REDICT_REPLY_ARRAY: case REDICT_REPLY_MAP: case REDICT_REPLY_ATTR: case REDICT_REPLY_SET: case REDICT_REPLY_PUSH: return processAggregateItem(r); default: assert(NULL); return REDICT_ERR; /* Avoid warning. */ } } redictReader *redictReaderCreateWithFunctions(redictReplyObjectFunctions *fn) { redictReader *r; r = hi_calloc(1,sizeof(redictReader)); if (r == NULL) return NULL; r->buf = sdsempty(); if (r->buf == NULL) goto oom; r->task = hi_calloc(REDICT_READER_STACK_SIZE, sizeof(*r->task)); if (r->task == NULL) goto oom; for (; r->tasks < REDICT_READER_STACK_SIZE; r->tasks++) { r->task[r->tasks] = hi_calloc(1, sizeof(**r->task)); if (r->task[r->tasks] == NULL) goto oom; } r->fn = fn; r->maxbuf = REDICT_READER_MAX_BUF; r->maxelements = REDICT_READER_MAX_ARRAY_ELEMENTS; r->ridx = -1; return r; oom: redictReaderFree(r); return NULL; } void redictReaderFree(redictReader *r) { if (r == NULL) return; if (r->reply != NULL && r->fn && r->fn->freeObject) r->fn->freeObject(r->reply); if (r->task) { /* We know r->task[i] is allocated if i < r->tasks */ for (int i = 0; i < r->tasks; i++) { hi_free(r->task[i]); } hi_free(r->task); } sdsfree(r->buf); hi_free(r); } int redictReaderFeed(redictReader *r, const char *buf, size_t len) { sds newbuf; /* Return early when this reader is in an erroneous state. */ if (r->err) return REDICT_ERR; /* Copy the provided buffer. */ if (buf != NULL && len >= 1) { /* Destroy internal buffer when it is empty and is quite large. */ if (r->len == 0 && r->maxbuf != 0 && sdsavail(r->buf) > r->maxbuf) { sdsfree(r->buf); r->buf = sdsempty(); if (r->buf == 0) goto oom; r->pos = 0; } newbuf = sdscatlen(r->buf,buf,len); if (newbuf == NULL) goto oom; r->buf = newbuf; r->len = sdslen(r->buf); } return REDICT_OK; oom: __redictReaderSetErrorOOM(r); return REDICT_ERR; } int redictReaderGetReply(redictReader *r, void **reply) { /* Default target pointer to NULL. */ if (reply != NULL) *reply = NULL; /* Return early when this reader is in an erroneous state. */ if (r->err) return REDICT_ERR; /* When the buffer is empty, there will never be a reply. */ if (r->len == 0) return REDICT_OK; /* Set first item to process when the stack is empty. */ if (r->ridx == -1) { r->task[0]->type = -1; r->task[0]->elements = -1; r->task[0]->idx = -1; r->task[0]->obj = NULL; r->task[0]->parent = NULL; r->task[0]->privdata = r->privdata; r->ridx = 0; } /* Process items in reply. */ while (r->ridx >= 0) if (processItem(r) != REDICT_OK) break; /* Return ASAP when an error occurred. */ if (r->err) return REDICT_ERR; /* Discard part of the buffer when we've consumed at least 1k, to avoid * doing unnecessary calls to memmove() in sds.c. */ if (r->pos >= 1024) { if (sdsrange(r->buf,r->pos,-1) < 0) return REDICT_ERR; r->pos = 0; r->len = sdslen(r->buf); } /* Emit a reply when there is one. */ if (r->ridx == -1) { if (reply != NULL) { *reply = r->reply; } else if (r->reply != NULL && r->fn && r->fn->freeObject) { r->fn->freeObject(r->reply); } r->reply = NULL; } return REDICT_OK; } hiredict/read.h000066400000000000000000000072531460333451400137470ustar00rootroot00000000000000/* * Copyright (c) 2009-2011, Salvatore Sanfilippo * Copyright (c) 2010-2011, Pieter Noordhuis * * SPDX-FileCopyrightText: 2024 Hiredict Contributors * SPDX-FileCopyrightText: 2024 Salvatore Sanfilippo * SPDX-FileCopyrightText: 2024 Pieter Noordhuis * * SPDX-License-Identifier: BSD-3-Clause * SPDX-License-Identifier: LGPL-3.0-or-later * */ #ifndef __HIREDICT_READ_H #define __HIREDICT_READ_H #include /* for size_t */ #define REDICT_ERR -1 #define REDICT_OK 0 /* When an error occurs, the err flag in a context is set to hold the type of * error that occurred. REDICT_ERR_IO means there was an I/O error and you * should use the "errno" variable to find out what is wrong. * For other values, the "errstr" field will hold a description. */ #define REDICT_ERR_IO 1 /* Error in read or write */ #define REDICT_ERR_EOF 3 /* End of file */ #define REDICT_ERR_PROTOCOL 4 /* Protocol error */ #define REDICT_ERR_OOM 5 /* Out of memory */ #define REDICT_ERR_TIMEOUT 6 /* Timed out */ #define REDICT_ERR_OTHER 2 /* Everything else... */ #define REDICT_REPLY_STRING 1 #define REDICT_REPLY_ARRAY 2 #define REDICT_REPLY_INTEGER 3 #define REDICT_REPLY_NIL 4 #define REDICT_REPLY_STATUS 5 #define REDICT_REPLY_ERROR 6 #define REDICT_REPLY_DOUBLE 7 #define REDICT_REPLY_BOOL 8 #define REDICT_REPLY_MAP 9 #define REDICT_REPLY_SET 10 #define REDICT_REPLY_ATTR 11 #define REDICT_REPLY_PUSH 12 #define REDICT_REPLY_BIGNUM 13 #define REDICT_REPLY_VERB 14 /* Default max unused reader buffer. */ #define REDICT_READER_MAX_BUF (1024*16) /* Default multi-bulk element limit */ #define REDICT_READER_MAX_ARRAY_ELEMENTS ((1LL<<32) - 1) #ifdef __cplusplus extern "C" { #endif typedef struct redictReadTask { int type; long long elements; /* number of elements in multibulk container */ int idx; /* index in parent (array) object */ void *obj; /* holds user-generated value for a read task */ struct redictReadTask *parent; /* parent task */ void *privdata; /* user-settable arbitrary field */ } redictReadTask; typedef struct redictReplyObjectFunctions { void *(*createString)(const redictReadTask*, char*, size_t); void *(*createArray)(const redictReadTask*, size_t); void *(*createInteger)(const redictReadTask*, long long); void *(*createDouble)(const redictReadTask*, double, char*, size_t); void *(*createNil)(const redictReadTask*); void *(*createBool)(const redictReadTask*, int); void (*freeObject)(void*); } redictReplyObjectFunctions; typedef struct redictReader { int err; /* Error flags, 0 when there is no error */ char errstr[128]; /* String representation of error when applicable */ char *buf; /* Read buffer */ size_t pos; /* Buffer cursor */ size_t len; /* Buffer length */ size_t maxbuf; /* Max length of unused buffer */ long long maxelements; /* Max multi-bulk elements */ redictReadTask **task; int tasks; int ridx; /* Index of current read task */ void *reply; /* Temporary reply pointer */ redictReplyObjectFunctions *fn; void *privdata; } redictReader; /* Public API for the protocol parser. */ redictReader *redictReaderCreateWithFunctions(redictReplyObjectFunctions *fn); void redictReaderFree(redictReader *r); int redictReaderFeed(redictReader *r, const char *buf, size_t len); int redictReaderGetReply(redictReader *r, void **reply); #define redictReaderSetPrivdata(_r, _p) (int)(((redictReader*)(_r))->privdata = (_p)) #define redictReaderGetObject(_r) (((redictReader*)(_r))->reply) #define redictReaderGetError(_r) (((redictReader*)(_r))->errstr) #ifdef __cplusplus } #endif #endif hiredict/sds.c000066400000000000000000001146641460333451400136250ustar00rootroot00000000000000/* SDSLib 2.0 -- A C dynamic strings library * * Copyright (c) 2006-2015, Salvatore Sanfilippo * Copyright (c) 2015, Oran Agra * Copyright (c) 2015, Redis Labs, Inc * * SPDX-FileCopyrightText: 2024 Hiredict Contributors * SPDX-FileCopyrightText: 2024 Salvatore Sanfilippo * SPDX-FileCopyrightText: 2024 Oran Agra * SPDX-FileCopyrightText: 2024 Redis Labs, Inc * * SPDX-License-Identifier: BSD-3-Clause * SPDX-License-Identifier: LGPL-3.0-or-later * */ #include "fmacros.h" #include #include #include #include #include #include #include "sds.h" #include "sdsalloc.h" static inline int sdsHdrSize(char type) { switch(type&SDS_TYPE_MASK) { case SDS_TYPE_5: return sizeof(struct sdshdr5); case SDS_TYPE_8: return sizeof(struct sdshdr8); case SDS_TYPE_16: return sizeof(struct sdshdr16); case SDS_TYPE_32: return sizeof(struct sdshdr32); case SDS_TYPE_64: return sizeof(struct sdshdr64); } return 0; } static inline char sdsReqType(size_t string_size) { if (string_size < 32) return SDS_TYPE_5; if (string_size < 0xff) return SDS_TYPE_8; if (string_size < 0xffff) return SDS_TYPE_16; if (string_size < 0xffffffff) return SDS_TYPE_32; return SDS_TYPE_64; } /* Create a new sds string with the content specified by the 'init' pointer * and 'initlen'. * If NULL is used for 'init' the string is initialized with zero bytes. * * The string is always null-terminated (all the sds strings are, always) so * even if you create an sds string with: * * mystring = sdsnewlen("abc",3); * * You can print the string with printf() as there is an implicit \0 at the * end of the string. However the string is binary safe and can contain * \0 characters in the middle, as the length is stored in the sds header. */ sds sdsnewlen(const void *init, size_t initlen) { void *sh; sds s; char type = sdsReqType(initlen); /* Empty strings are usually created in order to append. Use type 8 * since type 5 is not good at this. */ if (type == SDS_TYPE_5 && initlen == 0) type = SDS_TYPE_8; int hdrlen = sdsHdrSize(type); unsigned char *fp; /* flags pointer. */ if (hdrlen+initlen+1 <= initlen) return NULL; /* Catch size_t overflow */ sh = s_malloc(hdrlen+initlen+1); if (sh == NULL) return NULL; if (!init) memset(sh, 0, hdrlen+initlen+1); s = (char*)sh+hdrlen; fp = ((unsigned char*)s)-1; switch(type) { case SDS_TYPE_5: { *fp = type | (initlen << SDS_TYPE_BITS); break; } case SDS_TYPE_8: { SDS_HDR_VAR(8,s); sh->len = initlen; sh->alloc = initlen; *fp = type; break; } case SDS_TYPE_16: { SDS_HDR_VAR(16,s); sh->len = initlen; sh->alloc = initlen; *fp = type; break; } case SDS_TYPE_32: { SDS_HDR_VAR(32,s); sh->len = initlen; sh->alloc = initlen; *fp = type; break; } case SDS_TYPE_64: { SDS_HDR_VAR(64,s); sh->len = initlen; sh->alloc = initlen; *fp = type; break; } } if (initlen && init) memcpy(s, init, initlen); s[initlen] = '\0'; return s; } /* Create an empty (zero length) sds string. Even in this case the string * always has an implicit null term. */ sds sdsempty(void) { return sdsnewlen("",0); } /* Create a new sds string starting from a null terminated C string. */ sds sdsnew(const char *init) { size_t initlen = (init == NULL) ? 0 : strlen(init); return sdsnewlen(init, initlen); } /* Duplicate an sds string. */ sds sdsdup(const sds s) { return sdsnewlen(s, sdslen(s)); } /* Free an sds string. No operation is performed if 's' is NULL. */ void sdsfree(sds s) { if (s == NULL) return; s_free((char*)s-sdsHdrSize(s[-1])); } /* Set the sds string length to the length as obtained with strlen(), so * considering as content only up to the first null term character. * * This function is useful when the sds string is hacked manually in some * way, like in the following example: * * s = sdsnew("foobar"); * s[2] = '\0'; * sdsupdatelen(s); * printf("%d\n", sdslen(s)); * * The output will be "2", but if we comment out the call to sdsupdatelen() * the output will be "6" as the string was modified but the logical length * remains 6 bytes. */ void sdsupdatelen(sds s) { size_t reallen = strlen(s); sdssetlen(s, reallen); } /* Modify an sds string in-place to make it empty (zero length). * However all the existing buffer is not discarded but set as free space * so that next append operations will not require allocations up to the * number of bytes previously available. */ void sdsclear(sds s) { sdssetlen(s, 0); s[0] = '\0'; } /* Enlarge the free space at the end of the sds string so that the caller * is sure that after calling this function can overwrite up to addlen * bytes after the end of the string, plus one more byte for nul term. * * Note: this does not change the *length* of the sds string as returned * by sdslen(), but only the free buffer space we have. */ sds sdsMakeRoomFor(sds s, size_t addlen) { void *sh, *newsh; size_t avail = sdsavail(s); size_t len, newlen, reqlen; char type, oldtype = s[-1] & SDS_TYPE_MASK; int hdrlen; /* Return ASAP if there is enough space left. */ if (avail >= addlen) return s; len = sdslen(s); sh = (char*)s-sdsHdrSize(oldtype); reqlen = newlen = (len+addlen); if (newlen <= len) return NULL; /* Catch size_t overflow */ if (newlen < SDS_MAX_PREALLOC) newlen *= 2; else newlen += SDS_MAX_PREALLOC; type = sdsReqType(newlen); /* Don't use type 5: the user is appending to the string and type 5 is * not able to remember empty space, so sdsMakeRoomFor() must be called * at every appending operation. */ if (type == SDS_TYPE_5) type = SDS_TYPE_8; hdrlen = sdsHdrSize(type); if (hdrlen+newlen+1 <= reqlen) return NULL; /* Catch size_t overflow */ if (oldtype==type) { newsh = s_realloc(sh, hdrlen+newlen+1); if (newsh == NULL) return NULL; s = (char*)newsh+hdrlen; } else { /* Since the header size changes, need to move the string forward, * and can't use realloc */ newsh = s_malloc(hdrlen+newlen+1); if (newsh == NULL) return NULL; memcpy((char*)newsh+hdrlen, s, len+1); s_free(sh); s = (char*)newsh+hdrlen; s[-1] = type; sdssetlen(s, len); } sdssetalloc(s, newlen); return s; } /* Reallocate the sds string so that it has no free space at the end. The * contained string remains not altered, but next concatenation operations * will require a reallocation. * * After the call, the passed sds string is no longer valid and all the * references must be substituted with the new pointer returned by the call. */ sds sdsRemoveFreeSpace(sds s) { void *sh, *newsh; char type, oldtype = s[-1] & SDS_TYPE_MASK; int hdrlen; size_t len = sdslen(s); sh = (char*)s-sdsHdrSize(oldtype); type = sdsReqType(len); hdrlen = sdsHdrSize(type); if (oldtype==type) { newsh = s_realloc(sh, hdrlen+len+1); if (newsh == NULL) return NULL; s = (char*)newsh+hdrlen; } else { newsh = s_malloc(hdrlen+len+1); if (newsh == NULL) return NULL; memcpy((char*)newsh+hdrlen, s, len+1); s_free(sh); s = (char*)newsh+hdrlen; s[-1] = type; sdssetlen(s, len); } sdssetalloc(s, len); return s; } /* Return the total size of the allocation of the specifed sds string, * including: * 1) The sds header before the pointer. * 2) The string. * 3) The free buffer at the end if any. * 4) The implicit null term. */ size_t sdsAllocSize(sds s) { size_t alloc = sdsalloc(s); return sdsHdrSize(s[-1])+alloc+1; } /* Return the pointer of the actual SDS allocation (normally SDS strings * are referenced by the start of the string buffer). */ void *sdsAllocPtr(sds s) { return (void*) (s-sdsHdrSize(s[-1])); } /* Increment the sds length and decrements the left free space at the * end of the string according to 'incr'. Also set the null term * in the new end of the string. * * This function is used in order to fix the string length after the * user calls sdsMakeRoomFor(), writes something after the end of * the current string, and finally needs to set the new length. * * Note: it is possible to use a negative increment in order to * right-trim the string. * * Usage example: * * Using sdsIncrLen() and sdsMakeRoomFor() it is possible to mount the * following schema, to cat bytes coming from the kernel to the end of an * sds string without copying into an intermediate buffer: * * oldlen = sdslen(s); * s = sdsMakeRoomFor(s, BUFFER_SIZE); * nread = read(fd, s+oldlen, BUFFER_SIZE); * ... check for nread <= 0 and handle it ... * sdsIncrLen(s, nread); */ void sdsIncrLen(sds s, int incr) { unsigned char flags = s[-1]; size_t len; switch(flags&SDS_TYPE_MASK) { case SDS_TYPE_5: { unsigned char *fp = ((unsigned char*)s)-1; unsigned char oldlen = SDS_TYPE_5_LEN(flags); assert((incr > 0 && oldlen+incr < 32) || (incr < 0 && oldlen >= (unsigned int)(-incr))); *fp = SDS_TYPE_5 | ((oldlen+incr) << SDS_TYPE_BITS); len = oldlen+incr; break; } case SDS_TYPE_8: { SDS_HDR_VAR(8,s); assert((incr >= 0 && sh->alloc-sh->len >= incr) || (incr < 0 && sh->len >= (unsigned int)(-incr))); len = (sh->len += incr); break; } case SDS_TYPE_16: { SDS_HDR_VAR(16,s); assert((incr >= 0 && sh->alloc-sh->len >= incr) || (incr < 0 && sh->len >= (unsigned int)(-incr))); len = (sh->len += incr); break; } case SDS_TYPE_32: { SDS_HDR_VAR(32,s); assert((incr >= 0 && sh->alloc-sh->len >= (unsigned int)incr) || (incr < 0 && sh->len >= (unsigned int)(-incr))); len = (sh->len += incr); break; } case SDS_TYPE_64: { SDS_HDR_VAR(64,s); assert((incr >= 0 && sh->alloc-sh->len >= (uint64_t)incr) || (incr < 0 && sh->len >= (uint64_t)(-incr))); len = (sh->len += incr); break; } default: len = 0; /* Just to avoid compilation warnings. */ } s[len] = '\0'; } /* Grow the sds to have the specified length. Bytes that were not part of * the original length of the sds will be set to zero. * * if the specified length is smaller than the current length, no operation * is performed. */ sds sdsgrowzero(sds s, size_t len) { size_t curlen = sdslen(s); if (len <= curlen) return s; s = sdsMakeRoomFor(s,len-curlen); if (s == NULL) return NULL; /* Make sure added region doesn't contain garbage */ memset(s+curlen,0,(len-curlen+1)); /* also set trailing \0 byte */ sdssetlen(s, len); return s; } /* Append the specified binary-safe string pointed by 't' of 'len' bytes to the * end of the specified sds string 's'. * * After the call, the passed sds string is no longer valid and all the * references must be substituted with the new pointer returned by the call. */ sds sdscatlen(sds s, const void *t, size_t len) { size_t curlen = sdslen(s); s = sdsMakeRoomFor(s,len); if (s == NULL) return NULL; memcpy(s+curlen, t, len); sdssetlen(s, curlen+len); s[curlen+len] = '\0'; return s; } /* Append the specified null termianted C string to the sds string 's'. * * After the call, the passed sds string is no longer valid and all the * references must be substituted with the new pointer returned by the call. */ sds sdscat(sds s, const char *t) { return sdscatlen(s, t, strlen(t)); } /* Append the specified sds 't' to the existing sds 's'. * * After the call, the modified sds string is no longer valid and all the * references must be substituted with the new pointer returned by the call. */ sds sdscatsds(sds s, const sds t) { return sdscatlen(s, t, sdslen(t)); } /* Destructively modify the sds string 's' to hold the specified binary * safe string pointed by 't' of length 'len' bytes. */ sds sdscpylen(sds s, const char *t, size_t len) { if (sdsalloc(s) < len) { s = sdsMakeRoomFor(s,len-sdslen(s)); if (s == NULL) return NULL; } memcpy(s, t, len); s[len] = '\0'; sdssetlen(s, len); return s; } /* Like sdscpylen() but 't' must be a null-terminated string so that the length * of the string is obtained with strlen(). */ sds sdscpy(sds s, const char *t) { return sdscpylen(s, t, strlen(t)); } /* Helper for sdscatlonglong() doing the actual number -> string * conversion. 's' must point to a string with room for at least * SDS_LLSTR_SIZE bytes. * * The function returns the length of the null-terminated string * representation stored at 's'. */ #define SDS_LLSTR_SIZE 21 int sdsll2str(char *s, long long value) { char *p, aux; unsigned long long v; size_t l; /* Generate the string representation, this method produces * an reversed string. */ v = (value < 0) ? -value : value; p = s; do { *p++ = '0'+(v%10); v /= 10; } while(v); if (value < 0) *p++ = '-'; /* Compute length and add null term. */ l = p-s; *p = '\0'; /* Reverse the string. */ p--; while(s < p) { aux = *s; *s = *p; *p = aux; s++; p--; } return l; } /* Identical sdsll2str(), but for unsigned long long type. */ int sdsull2str(char *s, unsigned long long v) { char *p, aux; size_t l; /* Generate the string representation, this method produces * an reversed string. */ p = s; do { *p++ = '0'+(v%10); v /= 10; } while(v); /* Compute length and add null term. */ l = p-s; *p = '\0'; /* Reverse the string. */ p--; while(s < p) { aux = *s; *s = *p; *p = aux; s++; p--; } return l; } /* Create an sds string from a long long value. It is much faster than: * * sdscatprintf(sdsempty(),"%lld\n", value); */ sds sdsfromlonglong(long long value) { char buf[SDS_LLSTR_SIZE]; int len = sdsll2str(buf,value); return sdsnewlen(buf,len); } /* Like sdscatprintf() but gets va_list instead of being variadic. */ sds sdscatvprintf(sds s, const char *fmt, va_list ap) { va_list cpy; char staticbuf[1024], *buf = staticbuf, *t; size_t buflen = strlen(fmt)*2; /* We try to start using a static buffer for speed. * If not possible we revert to heap allocation. */ if (buflen > sizeof(staticbuf)) { buf = s_malloc(buflen); if (buf == NULL) return NULL; } else { buflen = sizeof(staticbuf); } /* Try with buffers two times bigger every time we fail to * fit the string in the current buffer size. */ while(1) { buf[buflen-2] = '\0'; va_copy(cpy,ap); vsnprintf(buf, buflen, fmt, cpy); va_end(cpy); if (buf[buflen-2] != '\0') { if (buf != staticbuf) s_free(buf); buflen *= 2; buf = s_malloc(buflen); if (buf == NULL) return NULL; continue; } break; } /* Finally concat the obtained string to the SDS string and return it. */ t = sdscat(s, buf); if (buf != staticbuf) s_free(buf); return t; } /* Append to the sds string 's' a string obtained using printf-alike format * specifier. * * After the call, the modified sds string is no longer valid and all the * references must be substituted with the new pointer returned by the call. * * Example: * * s = sdsnew("Sum is: "); * s = sdscatprintf(s,"%d+%d = %d",a,b,a+b). * * Often you need to create a string from scratch with the printf-alike * format. When this is the need, just use sdsempty() as the target string: * * s = sdscatprintf(sdsempty(), "... your format ...", args); */ sds sdscatprintf(sds s, const char *fmt, ...) { va_list ap; char *t; va_start(ap, fmt); t = sdscatvprintf(s,fmt,ap); va_end(ap); return t; } /* This function is similar to sdscatprintf, but much faster as it does * not rely on sprintf() family functions implemented by the libc that * are often very slow. Moreover directly handling the sds string as * new data is concatenated provides a performance improvement. * * However this function only handles an incompatible subset of printf-alike * format specifiers: * * %s - C String * %S - SDS string * %i - signed int * %I - 64 bit signed integer (long long, int64_t) * %u - unsigned int * %U - 64 bit unsigned integer (unsigned long long, uint64_t) * %% - Verbatim "%" character. */ sds sdscatfmt(sds s, char const *fmt, ...) { const char *f = fmt; long i; va_list ap; va_start(ap,fmt); i = sdslen(s); /* Position of the next byte to write to dest str. */ while(*f) { char next, *str; size_t l; long long num; unsigned long long unum; /* Make sure there is always space for at least 1 char. */ if (sdsavail(s)==0) { s = sdsMakeRoomFor(s,1); if (s == NULL) goto fmt_error; } switch(*f) { case '%': next = *(f+1); f++; switch(next) { case 's': case 'S': str = va_arg(ap,char*); l = (next == 's') ? strlen(str) : sdslen(str); if (sdsavail(s) < l) { s = sdsMakeRoomFor(s,l); if (s == NULL) goto fmt_error; } memcpy(s+i,str,l); sdsinclen(s,l); i += l; break; case 'i': case 'I': if (next == 'i') num = va_arg(ap,int); else num = va_arg(ap,long long); { char buf[SDS_LLSTR_SIZE]; l = sdsll2str(buf,num); if (sdsavail(s) < l) { s = sdsMakeRoomFor(s,l); if (s == NULL) goto fmt_error; } memcpy(s+i,buf,l); sdsinclen(s,l); i += l; } break; case 'u': case 'U': if (next == 'u') unum = va_arg(ap,unsigned int); else unum = va_arg(ap,unsigned long long); { char buf[SDS_LLSTR_SIZE]; l = sdsull2str(buf,unum); if (sdsavail(s) < l) { s = sdsMakeRoomFor(s,l); if (s == NULL) goto fmt_error; } memcpy(s+i,buf,l); sdsinclen(s,l); i += l; } break; default: /* Handle %% and generally %. */ s[i++] = next; sdsinclen(s,1); break; } break; default: s[i++] = *f; sdsinclen(s,1); break; } f++; } va_end(ap); /* Add null-term */ s[i] = '\0'; return s; fmt_error: va_end(ap); return NULL; } /* Remove the part of the string from left and from right composed just of * contiguous characters found in 'cset', that is a null terminted C string. * * After the call, the modified sds string is no longer valid and all the * references must be substituted with the new pointer returned by the call. * * Example: * * s = sdsnew("AA...AA.a.aa.aHelloWorld :::"); * s = sdstrim(s,"Aa. :"); * printf("%s\n", s); * * Output will be just "Hello World". */ sds sdstrim(sds s, const char *cset) { char *start, *end, *sp, *ep; size_t len; sp = start = s; ep = end = s+sdslen(s)-1; while(sp <= end && strchr(cset, *sp)) sp++; while(ep > sp && strchr(cset, *ep)) ep--; len = (sp > ep) ? 0 : ((ep-sp)+1); if (s != sp) memmove(s, sp, len); s[len] = '\0'; sdssetlen(s,len); return s; } /* Turn the string into a smaller (or equal) string containing only the * substring specified by the 'start' and 'end' indexes. * * start and end can be negative, where -1 means the last character of the * string, -2 the penultimate character, and so forth. * * The interval is inclusive, so the start and end characters will be part * of the resulting string. * * The string is modified in-place. * * Return value: * -1 (error) if sdslen(s) is larger than maximum positive ssize_t value. * 0 on success. * * Example: * * s = sdsnew("Hello World"); * sdsrange(s,1,-1); => "ello World" */ int sdsrange(sds s, ssize_t start, ssize_t end) { size_t newlen, len = sdslen(s); if (len > SSIZE_MAX) return -1; if (len == 0) return 0; if (start < 0) { start = len+start; if (start < 0) start = 0; } if (end < 0) { end = len+end; if (end < 0) end = 0; } newlen = (start > end) ? 0 : (end-start)+1; if (newlen != 0) { if (start >= (ssize_t)len) { newlen = 0; } else if (end >= (ssize_t)len) { end = len-1; newlen = (start > end) ? 0 : (end-start)+1; } } else { start = 0; } if (start && newlen) memmove(s, s+start, newlen); s[newlen] = 0; sdssetlen(s,newlen); return 0; } /* Apply tolower() to every character of the sds string 's'. */ void sdstolower(sds s) { size_t len = sdslen(s), j; for (j = 0; j < len; j++) s[j] = tolower(s[j]); } /* Apply toupper() to every character of the sds string 's'. */ void sdstoupper(sds s) { size_t len = sdslen(s), j; for (j = 0; j < len; j++) s[j] = toupper(s[j]); } /* Compare two sds strings s1 and s2 with memcmp(). * * Return value: * * positive if s1 > s2. * negative if s1 < s2. * 0 if s1 and s2 are exactly the same binary string. * * If two strings share exactly the same prefix, but one of the two has * additional characters, the longer string is considered to be greater than * the smaller one. */ int sdscmp(const sds s1, const sds s2) { size_t l1, l2, minlen; int cmp; l1 = sdslen(s1); l2 = sdslen(s2); minlen = (l1 < l2) ? l1 : l2; cmp = memcmp(s1,s2,minlen); if (cmp == 0) return l1-l2; return cmp; } /* Split 's' with separator in 'sep'. An array * of sds strings is returned. *count will be set * by reference to the number of tokens returned. * * On out of memory, zero length string, zero length * separator, NULL is returned. * * Note that 'sep' is able to split a string using * a multi-character separator. For example * sdssplit("foo_-_bar","_-_"); will return two * elements "foo" and "bar". * * This version of the function is binary-safe but * requires length arguments. sdssplit() is just the * same function but for zero-terminated strings. */ sds *sdssplitlen(const char *s, int len, const char *sep, int seplen, int *count) { int elements = 0, slots = 5, start = 0, j; sds *tokens; if (seplen < 1 || len < 0) return NULL; tokens = s_malloc(sizeof(sds)*slots); if (tokens == NULL) return NULL; if (len == 0) { *count = 0; return tokens; } for (j = 0; j < (len-(seplen-1)); j++) { /* make sure there is room for the next element and the final one */ if (slots < elements+2) { sds *newtokens; slots *= 2; newtokens = s_realloc(tokens,sizeof(sds)*slots); if (newtokens == NULL) goto cleanup; tokens = newtokens; } /* search the separator */ if ((seplen == 1 && *(s+j) == sep[0]) || (memcmp(s+j,sep,seplen) == 0)) { tokens[elements] = sdsnewlen(s+start,j-start); if (tokens[elements] == NULL) goto cleanup; elements++; start = j+seplen; j = j+seplen-1; /* skip the separator */ } } /* Add the final element. We are sure there is room in the tokens array. */ tokens[elements] = sdsnewlen(s+start,len-start); if (tokens[elements] == NULL) goto cleanup; elements++; *count = elements; return tokens; cleanup: { int i; for (i = 0; i < elements; i++) sdsfree(tokens[i]); s_free(tokens); *count = 0; return NULL; } } /* Free the result returned by sdssplitlen(), or do nothing if 'tokens' is NULL. */ void sdsfreesplitres(sds *tokens, int count) { if (!tokens) return; while(count--) sdsfree(tokens[count]); s_free(tokens); } /* Append to the sds string "s" an escaped string representation where * all the non-printable characters (tested with isprint()) are turned into * escapes in the form "\n\r\a...." or "\x". * * After the call, the modified sds string is no longer valid and all the * references must be substituted with the new pointer returned by the call. */ sds sdscatrepr(sds s, const char *p, size_t len) { s = sdscatlen(s,"\"",1); while(len--) { switch(*p) { case '\\': case '"': s = sdscatprintf(s,"\\%c",*p); break; case '\n': s = sdscatlen(s,"\\n",2); break; case '\r': s = sdscatlen(s,"\\r",2); break; case '\t': s = sdscatlen(s,"\\t",2); break; case '\a': s = sdscatlen(s,"\\a",2); break; case '\b': s = sdscatlen(s,"\\b",2); break; default: if (isprint((int) *p)) s = sdscatprintf(s,"%c",*p); else s = sdscatprintf(s,"\\x%02x",(unsigned char)*p); break; } p++; } return sdscatlen(s,"\"",1); } /* Helper function for sdssplitargs() that converts a hex digit into an * integer from 0 to 15 */ int hex_digit_to_int(char c) { switch(c) { case '0': return 0; case '1': return 1; case '2': return 2; case '3': return 3; case '4': return 4; case '5': return 5; case '6': return 6; case '7': return 7; case '8': return 8; case '9': return 9; case 'a': case 'A': return 10; case 'b': case 'B': return 11; case 'c': case 'C': return 12; case 'd': case 'D': return 13; case 'e': case 'E': return 14; case 'f': case 'F': return 15; default: return 0; } } /* Split a line into arguments, where every argument can be in the * following programming-language REPL-alike form: * * foo bar "newline are supported\n" and "\xff\x00otherstuff" * * The number of arguments is stored into *argc, and an array * of sds is returned. * * The caller should free the resulting array of sds strings with * sdsfreesplitres(). * * Note that sdscatrepr() is able to convert back a string into * a quoted string in the same format sdssplitargs() is able to parse. * * The function returns the allocated tokens on success, even when the * input string is empty, or NULL if the input contains unbalanced * quotes or closed quotes followed by non space characters * as in: "foo"bar or "foo' */ sds *sdssplitargs(const char *line, int *argc) { const char *p = line; char *current = NULL; char **vector = NULL; *argc = 0; while(1) { /* skip blanks */ while(*p && isspace((int) *p)) p++; if (*p) { /* get a token */ int inq=0; /* set to 1 if we are in "quotes" */ int insq=0; /* set to 1 if we are in 'single quotes' */ int done=0; if (current == NULL) current = sdsempty(); while(!done) { if (inq) { if (*p == '\\' && *(p+1) == 'x' && isxdigit((int) *(p+2)) && isxdigit((int) *(p+3))) { unsigned char byte; byte = (hex_digit_to_int(*(p+2))*16)+ hex_digit_to_int(*(p+3)); current = sdscatlen(current,(char*)&byte,1); p += 3; } else if (*p == '\\' && *(p+1)) { char c; p++; switch(*p) { case 'n': c = '\n'; break; case 'r': c = '\r'; break; case 't': c = '\t'; break; case 'b': c = '\b'; break; case 'a': c = '\a'; break; default: c = *p; break; } current = sdscatlen(current,&c,1); } else if (*p == '"') { /* closing quote must be followed by a space or * nothing at all. */ if (*(p+1) && !isspace((int) *(p+1))) goto err; done=1; } else if (!*p) { /* unterminated quotes */ goto err; } else { current = sdscatlen(current,p,1); } } else if (insq) { if (*p == '\\' && *(p+1) == '\'') { p++; current = sdscatlen(current,"'",1); } else if (*p == '\'') { /* closing quote must be followed by a space or * nothing at all. */ if (*(p+1) && !isspace((int) *(p+1))) goto err; done=1; } else if (!*p) { /* unterminated quotes */ goto err; } else { current = sdscatlen(current,p,1); } } else { switch(*p) { case ' ': case '\n': case '\r': case '\t': case '\0': done=1; break; case '"': inq=1; break; case '\'': insq=1; break; default: current = sdscatlen(current,p,1); break; } } if (*p) p++; } /* add the token to the vector */ { char **new_vector = s_realloc(vector,((*argc)+1)*sizeof(char*)); if (new_vector == NULL) { s_free(vector); return NULL; } vector = new_vector; vector[*argc] = current; (*argc)++; current = NULL; } } else { /* Even on empty input string return something not NULL. */ if (vector == NULL) vector = s_malloc(sizeof(void*)); return vector; } } err: while((*argc)--) sdsfree(vector[*argc]); s_free(vector); if (current) sdsfree(current); *argc = 0; return NULL; } /* Modify the string substituting all the occurrences of the set of * characters specified in the 'from' string to the corresponding character * in the 'to' array. * * For instance: sdsmapchars(mystring, "ho", "01", 2) * will have the effect of turning the string "hello" into "0ell1". * * The function returns the sds string pointer, that is always the same * as the input pointer since no resize is needed. */ sds sdsmapchars(sds s, const char *from, const char *to, size_t setlen) { size_t j, i, l = sdslen(s); for (j = 0; j < l; j++) { for (i = 0; i < setlen; i++) { if (s[j] == from[i]) { s[j] = to[i]; break; } } } return s; } /* Join an array of C strings using the specified separator (also a C string). * Returns the result as an sds string. */ sds sdsjoin(char **argv, int argc, char *sep) { sds join = sdsempty(); int j; for (j = 0; j < argc; j++) { join = sdscat(join, argv[j]); if (j != argc-1) join = sdscat(join,sep); } return join; } /* Like sdsjoin, but joins an array of SDS strings. */ sds sdsjoinsds(sds *argv, int argc, const char *sep, size_t seplen) { sds join = sdsempty(); int j; for (j = 0; j < argc; j++) { join = sdscatsds(join, argv[j]); if (j != argc-1) join = sdscatlen(join,sep,seplen); } return join; } /* Wrappers to the allocators used by SDS. Note that SDS will actually * just use the macros defined into sdsalloc.h in order to avoid to pay * the overhead of function calls. Here we define these wrappers only for * the programs SDS is linked to, if they want to touch the SDS internals * even if they use a different allocator. */ void *sds_malloc(size_t size) { return s_malloc(size); } void *sds_realloc(void *ptr, size_t size) { return s_realloc(ptr,size); } void sds_free(void *ptr) { s_free(ptr); } #if defined(SDS_TEST_MAIN) #include #include "testhelp.h" #include "limits.h" #define UNUSED(x) (void)(x) int sdsTest(void) { { sds x = sdsnew("foo"), y; test_cond("Create a string and obtain the length", sdslen(x) == 3 && memcmp(x,"foo\0",4) == 0) sdsfree(x); x = sdsnewlen("foo",2); test_cond("Create a string with specified length", sdslen(x) == 2 && memcmp(x,"fo\0",3) == 0) x = sdscat(x,"bar"); test_cond("Strings concatenation", sdslen(x) == 5 && memcmp(x,"fobar\0",6) == 0); x = sdscpy(x,"a"); test_cond("sdscpy() against an originally longer string", sdslen(x) == 1 && memcmp(x,"a\0",2) == 0) x = sdscpy(x,"xyzxxxxxxxxxxyyyyyyyyyykkkkkkkkkk"); test_cond("sdscpy() against an originally shorter string", sdslen(x) == 33 && memcmp(x,"xyzxxxxxxxxxxyyyyyyyyyykkkkkkkkkk\0",33) == 0) sdsfree(x); x = sdscatprintf(sdsempty(),"%d",123); test_cond("sdscatprintf() seems working in the base case", sdslen(x) == 3 && memcmp(x,"123\0",4) == 0) sdsfree(x); x = sdsnew("--"); x = sdscatfmt(x, "Hello %s World %I,%I--", "Hi!", LLONG_MIN,LLONG_MAX); test_cond("sdscatfmt() seems working in the base case", sdslen(x) == 60 && memcmp(x,"--Hello Hi! World -9223372036854775808," "9223372036854775807--",60) == 0) printf("[%s]\n",x); sdsfree(x); x = sdsnew("--"); x = sdscatfmt(x, "%u,%U--", UINT_MAX, ULLONG_MAX); test_cond("sdscatfmt() seems working with unsigned numbers", sdslen(x) == 35 && memcmp(x,"--4294967295,18446744073709551615--",35) == 0) sdsfree(x); x = sdsnew(" x "); sdstrim(x," x"); test_cond("sdstrim() works when all chars match", sdslen(x) == 0) sdsfree(x); x = sdsnew(" x "); sdstrim(x," "); test_cond("sdstrim() works when a single char remains", sdslen(x) == 1 && x[0] == 'x') sdsfree(x); x = sdsnew("xxciaoyyy"); sdstrim(x,"xy"); test_cond("sdstrim() correctly trims characters", sdslen(x) == 4 && memcmp(x,"ciao\0",5) == 0) y = sdsdup(x); sdsrange(y,1,1); test_cond("sdsrange(...,1,1)", sdslen(y) == 1 && memcmp(y,"i\0",2) == 0) sdsfree(y); y = sdsdup(x); sdsrange(y,1,-1); test_cond("sdsrange(...,1,-1)", sdslen(y) == 3 && memcmp(y,"iao\0",4) == 0) sdsfree(y); y = sdsdup(x); sdsrange(y,-2,-1); test_cond("sdsrange(...,-2,-1)", sdslen(y) == 2 && memcmp(y,"ao\0",3) == 0) sdsfree(y); y = sdsdup(x); sdsrange(y,2,1); test_cond("sdsrange(...,2,1)", sdslen(y) == 0 && memcmp(y,"\0",1) == 0) sdsfree(y); y = sdsdup(x); sdsrange(y,1,100); test_cond("sdsrange(...,1,100)", sdslen(y) == 3 && memcmp(y,"iao\0",4) == 0) sdsfree(y); y = sdsdup(x); sdsrange(y,100,100); test_cond("sdsrange(...,100,100)", sdslen(y) == 0 && memcmp(y,"\0",1) == 0) sdsfree(y); sdsfree(x); x = sdsnew("foo"); y = sdsnew("foa"); test_cond("sdscmp(foo,foa)", sdscmp(x,y) > 0) sdsfree(y); sdsfree(x); x = sdsnew("bar"); y = sdsnew("bar"); test_cond("sdscmp(bar,bar)", sdscmp(x,y) == 0) sdsfree(y); sdsfree(x); x = sdsnew("aar"); y = sdsnew("bar"); test_cond("sdscmp(bar,bar)", sdscmp(x,y) < 0) sdsfree(y); sdsfree(x); x = sdsnewlen("\a\n\0foo\r",7); y = sdscatrepr(sdsempty(),x,sdslen(x)); test_cond("sdscatrepr(...data...)", memcmp(y,"\"\\a\\n\\x00foo\\r\"",15) == 0) { unsigned int oldfree; char *p; int step = 10, j, i; sdsfree(x); sdsfree(y); x = sdsnew("0"); test_cond("sdsnew() free/len buffers", sdslen(x) == 1 && sdsavail(x) == 0); /* Run the test a few times in order to hit the first two * SDS header types. */ for (i = 0; i < 10; i++) { int oldlen = sdslen(x); x = sdsMakeRoomFor(x,step); int type = x[-1]&SDS_TYPE_MASK; test_cond("sdsMakeRoomFor() len", sdslen(x) == oldlen); if (type != SDS_TYPE_5) { test_cond("sdsMakeRoomFor() free", sdsavail(x) >= step); oldfree = sdsavail(x); } p = x+oldlen; for (j = 0; j < step; j++) { p[j] = 'A'+j; } sdsIncrLen(x,step); } test_cond("sdsMakeRoomFor() content", memcmp("0ABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJ",x,101) == 0); test_cond("sdsMakeRoomFor() final length",sdslen(x)==101); sdsfree(x); } } test_report() return 0; } #endif #ifdef SDS_TEST_MAIN int main(void) { return sdsTest(); } #endif hiredict/sds.h000066400000000000000000000175401460333451400136250ustar00rootroot00000000000000/* SDSLib 2.0 -- A C dynamic strings library * * Copyright (c) 2006-2015, Salvatore Sanfilippo * Copyright (c) 2015, Oran Agra * Copyright (c) 2015, Redis Labs, Inc * * SPDX-FileCopyrightText: 2024 Hiredict Contributors * SPDX-FileCopyrightText: 2024 Salvatore Sanfilippo * SPDX-FileCopyrightText: 2024 Oran Agra * SPDX-FileCopyrightText: 2024 Redis Labs, Inc * * SPDX-License-Identifier: BSD-3-Clause * SPDX-License-Identifier: LGPL-3.0-or-later * */ #ifndef __SDS_H #define __SDS_H #define SDS_MAX_PREALLOC (1024*1024) #ifdef _MSC_VER typedef long long ssize_t; #define SSIZE_MAX (LLONG_MAX >> 1) #ifndef __clang__ #define __attribute__(x) #endif #endif #include #include #include typedef char *sds; /* Note: sdshdr5 is never used, we just access the flags byte directly. * However is here to document the layout of type 5 SDS strings. */ struct __attribute__ ((__packed__)) sdshdr5 { unsigned char flags; /* 3 lsb of type, and 5 msb of string length */ char buf[]; }; struct __attribute__ ((__packed__)) sdshdr8 { uint8_t len; /* used */ uint8_t alloc; /* excluding the header and null terminator */ unsigned char flags; /* 3 lsb of type, 5 unused bits */ char buf[]; }; struct __attribute__ ((__packed__)) sdshdr16 { uint16_t len; /* used */ uint16_t alloc; /* excluding the header and null terminator */ unsigned char flags; /* 3 lsb of type, 5 unused bits */ char buf[]; }; struct __attribute__ ((__packed__)) sdshdr32 { uint32_t len; /* used */ uint32_t alloc; /* excluding the header and null terminator */ unsigned char flags; /* 3 lsb of type, 5 unused bits */ char buf[]; }; struct __attribute__ ((__packed__)) sdshdr64 { uint64_t len; /* used */ uint64_t alloc; /* excluding the header and null terminator */ unsigned char flags; /* 3 lsb of type, 5 unused bits */ char buf[]; }; #define SDS_TYPE_5 0 #define SDS_TYPE_8 1 #define SDS_TYPE_16 2 #define SDS_TYPE_32 3 #define SDS_TYPE_64 4 #define SDS_TYPE_MASK 7 #define SDS_TYPE_BITS 3 #define SDS_HDR_VAR(T,s) struct sdshdr##T *sh = (struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T))); #define SDS_HDR(T,s) ((struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T)))) #define SDS_TYPE_5_LEN(f) ((f)>>SDS_TYPE_BITS) static inline size_t sdslen(const sds s) { unsigned char flags = s[-1]; switch(flags&SDS_TYPE_MASK) { case SDS_TYPE_5: return SDS_TYPE_5_LEN(flags); case SDS_TYPE_8: return SDS_HDR(8,s)->len; case SDS_TYPE_16: return SDS_HDR(16,s)->len; case SDS_TYPE_32: return SDS_HDR(32,s)->len; case SDS_TYPE_64: return SDS_HDR(64,s)->len; } return 0; } static inline size_t sdsavail(const sds s) { unsigned char flags = s[-1]; switch(flags&SDS_TYPE_MASK) { case SDS_TYPE_5: { return 0; } case SDS_TYPE_8: { SDS_HDR_VAR(8,s); return sh->alloc - sh->len; } case SDS_TYPE_16: { SDS_HDR_VAR(16,s); return sh->alloc - sh->len; } case SDS_TYPE_32: { SDS_HDR_VAR(32,s); return sh->alloc - sh->len; } case SDS_TYPE_64: { SDS_HDR_VAR(64,s); return sh->alloc - sh->len; } } return 0; } static inline void sdssetlen(sds s, size_t newlen) { unsigned char flags = s[-1]; switch(flags&SDS_TYPE_MASK) { case SDS_TYPE_5: { unsigned char *fp = ((unsigned char*)s)-1; *fp = (unsigned char)(SDS_TYPE_5 | (newlen << SDS_TYPE_BITS)); } break; case SDS_TYPE_8: SDS_HDR(8,s)->len = (uint8_t)newlen; break; case SDS_TYPE_16: SDS_HDR(16,s)->len = (uint16_t)newlen; break; case SDS_TYPE_32: SDS_HDR(32,s)->len = (uint32_t)newlen; break; case SDS_TYPE_64: SDS_HDR(64,s)->len = (uint64_t)newlen; break; } } static inline void sdsinclen(sds s, size_t inc) { unsigned char flags = s[-1]; switch(flags&SDS_TYPE_MASK) { case SDS_TYPE_5: { unsigned char *fp = ((unsigned char*)s)-1; unsigned char newlen = SDS_TYPE_5_LEN(flags)+(unsigned char)inc; *fp = SDS_TYPE_5 | (newlen << SDS_TYPE_BITS); } break; case SDS_TYPE_8: SDS_HDR(8,s)->len += (uint8_t)inc; break; case SDS_TYPE_16: SDS_HDR(16,s)->len += (uint16_t)inc; break; case SDS_TYPE_32: SDS_HDR(32,s)->len += (uint32_t)inc; break; case SDS_TYPE_64: SDS_HDR(64,s)->len += (uint64_t)inc; break; } } /* sdsalloc() = sdsavail() + sdslen() */ static inline size_t sdsalloc(const sds s) { unsigned char flags = s[-1]; switch(flags&SDS_TYPE_MASK) { case SDS_TYPE_5: return SDS_TYPE_5_LEN(flags); case SDS_TYPE_8: return SDS_HDR(8,s)->alloc; case SDS_TYPE_16: return SDS_HDR(16,s)->alloc; case SDS_TYPE_32: return SDS_HDR(32,s)->alloc; case SDS_TYPE_64: return SDS_HDR(64,s)->alloc; } return 0; } static inline void sdssetalloc(sds s, size_t newlen) { unsigned char flags = s[-1]; switch(flags&SDS_TYPE_MASK) { case SDS_TYPE_5: /* Nothing to do, this type has no total allocation info. */ break; case SDS_TYPE_8: SDS_HDR(8,s)->alloc = (uint8_t)newlen; break; case SDS_TYPE_16: SDS_HDR(16,s)->alloc = (uint16_t)newlen; break; case SDS_TYPE_32: SDS_HDR(32,s)->alloc = (uint32_t)newlen; break; case SDS_TYPE_64: SDS_HDR(64,s)->alloc = (uint64_t)newlen; break; } } sds sdsnewlen(const void *init, size_t initlen); sds sdsnew(const char *init); sds sdsempty(void); sds sdsdup(const sds s); void sdsfree(sds s); sds sdsgrowzero(sds s, size_t len); sds sdscatlen(sds s, const void *t, size_t len); sds sdscat(sds s, const char *t); sds sdscatsds(sds s, const sds t); sds sdscpylen(sds s, const char *t, size_t len); sds sdscpy(sds s, const char *t); sds sdscatvprintf(sds s, const char *fmt, va_list ap); #ifdef __GNUC__ sds sdscatprintf(sds s, const char *fmt, ...) __attribute__((format(printf, 2, 3))); #else sds sdscatprintf(sds s, const char *fmt, ...); #endif sds sdscatfmt(sds s, char const *fmt, ...); sds sdstrim(sds s, const char *cset); int sdsrange(sds s, ssize_t start, ssize_t end); void sdsupdatelen(sds s); void sdsclear(sds s); int sdscmp(const sds s1, const sds s2); sds *sdssplitlen(const char *s, int len, const char *sep, int seplen, int *count); void sdsfreesplitres(sds *tokens, int count); void sdstolower(sds s); void sdstoupper(sds s); sds sdsfromlonglong(long long value); sds sdscatrepr(sds s, const char *p, size_t len); sds *sdssplitargs(const char *line, int *argc); sds sdsmapchars(sds s, const char *from, const char *to, size_t setlen); sds sdsjoin(char **argv, int argc, char *sep); sds sdsjoinsds(sds *argv, int argc, const char *sep, size_t seplen); /* Low level functions exposed to the user API */ sds sdsMakeRoomFor(sds s, size_t addlen); void sdsIncrLen(sds s, int incr); sds sdsRemoveFreeSpace(sds s); size_t sdsAllocSize(sds s); void *sdsAllocPtr(sds s); /* Export the allocator used by SDS to the program using SDS. * Sometimes the program SDS is linked to, may use a different set of * allocators, but may want to allocate or free things that SDS will * respectively free or allocate. */ void *sds_malloc(size_t size); void *sds_realloc(void *ptr, size_t size); void sds_free(void *ptr); #ifdef REDICT_TEST int sdsTest(int argc, char *argv[]); #endif #endif hiredict/sdsalloc.h000066400000000000000000000016221460333451400146320ustar00rootroot00000000000000/* SDSLib 2.0 -- A C dynamic strings library * * Copyright (c) 2006-2015, Salvatore Sanfilippo * Copyright (c) 2015, Oran Agra * Copyright (c) 2015, Redis Labs, Inc * * SPDX-FileCopyrightText: 2024 Hiredict Contributors * SPDX-FileCopyrightText: 2024 Salvatore Sanfilippo * SPDX-FileCopyrightText: 2024 Oran Agra * SPDX-FileCopyrightText: 2024 Redis Labs, Inc * * SPDX-License-Identifier: BSD-3-Clause * SPDX-License-Identifier: LGPL-3.0-or-later * */ /* SDS allocator selection. * * This file is used in order to change the SDS allocator at compile time. * Just define the following defines to what you want to use. Also add * the include of your alternate allocator if needed (not needed in order * to use the default libc allocator). */ #include "alloc.h" #define s_malloc hi_malloc #define s_realloc hi_realloc #define s_free hi_free hiredict/shim/000077500000000000000000000000001460333451400136145ustar00rootroot00000000000000hiredict/shim/adapters/000077500000000000000000000000001460333451400154175ustar00rootroot00000000000000hiredict/shim/adapters/ae.h000066400000000000000000000013501460333451400161540ustar00rootroot00000000000000/* * Copyright (c) 2010-2011, Pieter Noordhuis * * SPDX-FileCopyrightText: 2024 Hiredict Contributors * * SPDX-License-Identifier: BSD-3-Clause * SPDX-License-Identifier: LGPL-3.0-or-later * */ #ifndef __HIREDIS_AE_H__ #define __HIREDIS_AE_H__ #include #include #include #define redisAeEvents redictAeEvents #define redisAeReadEvent redictAeReadEvent #define redisAeWriteEvent redictAeWriteEvent #define redisAeAddRead redictAeAddRead #define redisAeDelRead redictAeDelRead #define redisAeAddWrite redisAeAddWrite #define redisAeDelWrite redictAeDelWrite #define redisAeCleanup redictAeCleanup #define redisAeAttach redictAeAttach #endif hiredict/shim/adapters/glib.h000066400000000000000000000017031460333451400165060ustar00rootroot00000000000000/* * Copyright (c) 2009-2011, Salvatore Sanfilippo * * SPDX-FileCopyrightText: 2024 Hiredict Contributors * * SPDX-License-Identifier: BSD-3-Clause * SPDX-License-Identifier: LGPL-3.0-or-later * */ #ifndef __HIREDIS_GLIB_H__ #define __HIREDIS_GLIB_H__ #include #include #include #define RedisSource RedictSource #define redis_source_add_read redict_source_add_read #define redis_source_del_read redict_source_del_read #define redis_source_add_write redict_source_add_write #define redis_source_del_write redict_source_del_write #define redis_source_cleanup redict_source_cleanup #define redis_source_prepare redict_source_prepare #define redis_source_check redict_source_check #define redis_source_dispatch redict_source_dispatch #define redis_source_finalize redict_source_finalize #define redis_source_new redict_source_new #endif /* __HIREDIS_GLIB_H__ */ hiredict/shim/adapters/ivykis.h000066400000000000000000000014751460333451400171150ustar00rootroot00000000000000/* * Copyright (c) 2009-2011, Salvatore Sanfilippo * * SPDX-FileCopyrightText: 2024 Hiredict Contributors * * SPDX-License-Identifier: BSD-3-Clause * SPDX-License-Identifier: LGPL-3.0-or-later * */ #ifndef __HIREDIS_IVYKIS_H__ #define __HIREDIS_IVYKIS_H__ #include #include #include #define redisIvykisEvents redictIvykisEvents #define redisIvykisReadEvent redictIvykisReadEvent #define redisIvykisWriteEvent redictIvykisWriteEvent #define redisIvykisAddRead redictIvykisAddRead #define redisIvykisDelRead redictIvykisDelRead #define redisIvykisAddWrite redictIvykisAddWrite #define redisIvykisDelWrite redictIvykisDelWrite #define redisIvykisCleanup redictIvykisCleanup #define redisIvykisAttach redictIvykisAttach #endif hiredict/shim/adapters/libev.h000066400000000000000000000017241460333451400166750ustar00rootroot00000000000000/* * Copyright (c) 2010-2011, Pieter Noordhuis * * All rights reserved. * * SPDX-FileCopyrightText: 2024 Hiredict Contributors * * SPDX-License-Identifier: BSD-3-Clause * SPDX-License-Identifier: LGPL-3.0-or-later * */ #ifndef __HIREDIS_LIBEV_H__ #define __HIREDIS_LIBEV_H__ #include #include #include #define redisLibevEvents redictLibevEvents #define redisLibevReadEvent redictLibevReadEvent #define redisLibevWriteEvent redictLibevWriteEvent #define redisLibevAddRead redictLibevAddRead #define redisLibevDelRead redictLibevDelRead #define redisLibevAddWrite redictLibevAddWrite #define redisLibevDelWrite redictLibevDelWrite #define redisLibevStopTimer redictLibevStopTimer #define redisLibevCleanup redictLibevCleanup #define redisLibevTimeout redictLibevTimeout #define redisLibevSetTimeout redictLibevSetTimeout #define redisLibevAttach redictLibevAttach #endif hiredict/shim/adapters/libevent.h000066400000000000000000000020661460333451400174040ustar00rootroot00000000000000/* * Copyright (c) 2010-2011, Pieter Noordhuis * * SPDX-FileCopyrightText: 2024 Hiredict Contributors * * SPDX-License-Identifier: BSD-3-Clause * SPDX-License-Identifier: LGPL-3.0-or-later * */ #ifndef __HIREDIS_LIBEVENT_H__ #define __HIREDIS_LIBEVENT_H__ #include #include #include #define REDIS_LIBEVENT_DELETED REDICT_LIBEVENT_DELETED #define REDIS_LIBEVENT_ENTERED REDICT_LIBEVENT_ENTERED #define redisLibeventEvents redictLibeventEvents #define redisLibeventDestroy redictLibeventDestroy #define redisLibeventHandler redictLibeventHandler #define redisLibeventUpdate redictLibeventUpdate #define redisLibeventAddRead redictLibeventAddRead #define redisLibeventDelRead redictLibeventDelRead #define redisLibeventAddWrite redictLibeventAddWrite #define redisLibeventDelWrite redictLibeventDelWrite #define redisLibeventCleanup redictLibeventCleanup #define redisLibeventSetTimeout redictLibeventSetTimeout #define redisLibeventAttach redictLibeventAttach #endif hiredict/shim/adapters/libhv.h000066400000000000000000000015341460333451400166770ustar00rootroot00000000000000/* * Copyright (c) 2009-2011, Salvatore Sanfilippo * * SPDX-FileCopyrightText: 2024 Hiredict Contributors * * SPDX-License-Identifier: BSD-3-Clause * SPDX-License-Identifier: LGPL-3.0-or-later * */ #ifndef __HIREDIS_LIBHV_H__ #define __HIREDIS_LIBHV_H__ #include #include #include #define redisLibhvEvents redictLibhvEvents #define redisLibhvHandleEvents redictLibhvHandleEvents #define redisLibhvAddRead redictLibhvAddRead #define redisLibhvDelRead redictLibhvDelRead #define redisLibhvAddWrite redictLibhvAddWrite #define redisLibhvDelWrite redictLibhvDelWrite #define redisLibhvCleanup redictLibhvCleanup #define redisLibhvTimeout redictLibhvTimeout #define redisLibhvSetTimeout redictLibhvSetTimeout #define redisLibhvAttach redictLibhvAttach #endif hiredict/shim/adapters/libsdevent.h000066400000000000000000000021711460333451400177300ustar00rootroot00000000000000/* * Copyright (c) 2009-2011, Salvatore Sanfilippo * * SPDX-FileCopyrightText: 2024 Hiredict Contributors * * SPDX-License-Identifier: BSD-3-Clause * SPDX-License-Identifier: LGPL-3.0-or-later * */ #ifndef HIREDIS_LIBSDEVENT_H #define HIREDIS_LIBSDEVENT_H #include #include #include #define REDIS_LIBSDEVENT_DELETED REDICT_LIBSDEVENT_DELETED #define REDIS_LIBSDEVENT_ENTERED REDICT_LIBSDEVENT_ENTERED #define redisLibsdeventEvents redictLibsdeventEvents #define redisLibsdeventDestroy redictLibsdeventDestroy #define redisLibsdeventTimeoutHandler redictLibsdeventTimeoutHandler #define redisLibsdeventHandler redictLibsdeventHandler #define redisLibsdeventAddRead redictLibsdeventAddRead #define redisLibsdeventDelRead redictLibsdeventDelRead #define redisLibsdeventAddWrite redictLibsdeventAddWrite #define redisLibsdeventDelWrite redictLibsdeventDelWrite #define redisLibsdeventCleanup redictLibsdeventCleanup #define redisLibsdeventSetTimeout redictLibsdeventSetTimeout #define redisLibsdeventAttach redictLibsdeventAttach #endif hiredict/shim/adapters/libuv.h000066400000000000000000000015141460333451400167120ustar00rootroot00000000000000/* * Copyright (c) 2009-2011, Salvatore Sanfilippo * * SPDX-FileCopyrightText: 2024 Hiredict Contributors * * SPDX-License-Identifier: BSD-3-Clause * SPDX-License-Identifier: LGPL-3.0-or-later * */ #ifndef __HIREDIS_LIBUV_H__ #define __HIREDIS_LIBUV_H__ #include #include #include #define redisLibuvEvents redictLibuvEvents #define redisLibuvPoll redictLibuvPoll #define redisLibuvAddRead redictLibuvAddRead #define redisLibuvDelRead redictLibuvDelRead #define redisLibuvAddWrite redictLibuvAddWrite #define redisLibuvDelWrite redictLibuvDelWrite #define redisLibuvTimeout redictLibuvTimeout #define redisLibuvSetTimeout redictLibuvSetTimeout #define redisLibuvCleanup redictLibuvCleanup #define redisLibuvAttach redictLibuvAttach #endif hiredict/shim/adapters/macosx.h000066400000000000000000000014001460333451400170550ustar00rootroot00000000000000/* * Copyright (c) 2015 Dmitry Bakhvalov * * SPDX-FileCopyrightText: 2024 Hiredict Contributors * * SPDX-License-Identifier: BSD-3-Clause * SPDX-License-Identifier: LGPL-3.0-or-later * */ #ifndef __HIREDIS_MACOSX_H__ #define __HIREDIS_MACOSX_H__ #include #include #include #define RedisRunLoop RedictRunLoop #define freeRedisRunLoop freeRedictRunLoop #define redisMacOSAddRead redictMacOSAddRead #define redisMacOSDelRead redictMacOSDelRead #define redisMacOSAddWrite redictMacOSAddWrite #define redisMacOSDelWrite redictMacOSDelWrite #define redisMacOSCleanup redictMacOSCleanup #define redisMacOSAsyncCallback redictMacOSAsyncCallback #define redisMacOSAttach redictMacOSAttach #endif hiredict/shim/adapters/poll.h000066400000000000000000000024331460333451400165400ustar00rootroot00000000000000/* * Copyright (c) 2009-2011, Salvatore Sanfilippo * * SPDX-FileCopyrightText: 2024 Hiredict Contributors * * SPDX-License-Identifier: BSD-3-Clause * SPDX-License-Identifier: LGPL-3.0-or-later * */ #ifndef HIREDIS_POLL_H #define HIREDIS_POLL_H #include #include #include /* Values to return from redictPollTick */ #define REDIS_POLL_HANDLED_READ REDICT_POLL_HANDLED_READ #define REDIS_POLL_HANDLED_WRITE REDICT_POLL_HANDLED_WRITE #define REDIS_POLL_HANDLED_TIMEOUT REDICT_POLL_HANDLED_TIMEOUT /* An adapter to allow manual polling of the async context by checking the state * of the underlying file descriptor. Useful in cases where there is no formal * IO event loop but regular ticking can be used, such as in game engines. */ #define redisPollEvents redictPollEvents #define redisPollTimevalToDouble redictPollTimevalToDouble #define redisPollGetNow redictPollGetNow #define redisPollTick redictPollTick #define redisPollAddRead redictPollAddRead #define redisPollDelRead redictPollDelRead #define redisPollAddWrite redictPollAddWrite #define redisPollDelWrite redictPollDelWrite #define redisPollCleanup redictPollCleanup #define redisPollAttach redictPollAttach #endif /* HIREDIS_POLL_H */ hiredict/shim/adapters/qt.h000066400000000000000000000011221460333451400162100ustar00rootroot00000000000000/* * Copyright (C) 2014 Pietro Cerutti * * SPDX-FileCopyrightText: 2024 Hiredict Contributors * * SPDX-License-Identifier: BSD-3-Clause * SPDX-License-Identifier: LGPL-3.0-or-later * */ #ifndef __HIREDIS_QT_H__ #define __HIREDIS_QT_H__ #include #include #define RedisQtAddRead RedictQtAddRead #define RedisQtDelRead RedictQtDelRead #define RedisQtAddWrite RedictQtAddWrite #define RedisQtDelWrite RedictQtDelWrite #define RedisQtCleanup RedictQtCleanup #define RedisQtAdapter RedictQtAdapter #endif /* !__HIREDIS_QT_H__ */ hiredict/shim/adapters/redictmoduleapi.h000066400000000000000000000021621460333451400207430ustar00rootroot00000000000000/* * Copyright (c) 2009-2011, Salvatore Sanfilippo * * SPDX-FileCopyrightText: 2024 Hiredict Contributors * SPDX-FileCopyrightText: 2024 Salvatore Sanfilippo * * SPDX-License-Identifier: BSD-3-Clause * SPDX-License-Identifier: LGPL-3.0-or-later * */ #ifndef __HIREDIS_REDICTMODULEAPI_H__ #define __HIREDIS_REDICTMODULEAPI_H__ #include "redictmodule.h" #include #include #define redisModuleEvents redictModuleEvents #define redisModuleReadEvent redictModuleReadEvent #define redisModuleWriteEvent redictModuleWriteEvent #define redisModuleAddRead redictModuleAddRead #define redisModuleDelRead redictModuleDelRead #define redisModuleAddWrite redictModuleAddWrite #define redisModuleDelWrite redictModuleDelWrite #define redisModuleStopTimer redictModuleStopTimer #define redisModuleCleanup redictModuleCleanup #define redisModuleTimeout redictModuleTimeout #define redisModuleSetTimeout redictModuleSetTimeout #define redisModuleCompatibilityCheck redictModuleCompatibilityCheck #define redisModuleAttach redictModuleAttach #endif hiredict/shim/alloc.h000066400000000000000000000010431460333451400150550ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2024 Hiredict Contributors * * SPDX-License-Identifier: BSD-3-Clause * SPDX-License-Identifier: LGPL-3.0-or-later * */ #ifndef __HIREDIS_ALLOC_H #define __HIREDIS_ALLOC_H #include /* Structure pointing to our actually configured allocators */ #define hiredisAllocFuncs hiredictAllocFuncs #define hiredisSetAllocators hiredictSetAllocators #define hiredisResetAllocators hiredictResetAllocators #ifndef _WIN32 #define hiredisAllocFns hiredictAllocFns; #endif #endif /* HIREDIS_ALLOC_H */ hiredict/shim/async.h000066400000000000000000000041471460333451400151100ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2024 Hiredict Contributors * * SPDX-License-Identifier: BSD-3-Clause * SPDX-License-Identifier: LGPL-3.0-or-later * */ #ifndef __HIREDIS_ASYNC_H #define __HIREDIS_ASYNC_H #include #include #define redisCallbackFn redictCallbackFn #define redisCallback redictCallback /* List of callbacks for either regular replies or pub/sub */ #define redisCallbackList redictCallbackList /* Connection callback prototypes */ #define redisDisconnectCallback redictDisconnectCallback #define redisConnectCallback redictConnectCallback #define redisConnectCallbackNc redictConnectCallbackNC #define redisTimerCallback redictTimerCallback /* Context for an async connection to Redict */ #define redisAsyncContext redictAsyncContext /* Functions that proxy to hiredict */ #define redisAsyncConnectWithOptions redictAsyncConnectWithOptions #define redisAsyncConnect redictAsyncConnect #define redisAsyncConnectBind redictAsyncConnectBind #define redisAsyncConnectBindWithReuse redictAsyncConnectBindWithReuse #define redisAsyncConnectUnix redictAsyncConnectUnix #define redisAsyncSetConnectCallback redictAsyncSetConnectCallback #define redisAsyncSetConnectCallbackNC redictAsyncSetConnectCallbackNC #define redisAsyncSetDisconnectCallback redictAsyncSetDisconnectCallback #define redisAsyncSetPushCallback redictAsyncSetPushCallback #define redisAsyncSetTimeout redictAsyncSetTimeout #define redisAsyncDisconnect redictAsyncDisconnect #define redisAsyncFree redictAsyncFree /* Handle read/write events */ #define redisAsyncHandleRead redictAsyncHandleRead #define redisAsyncHandleWrite redictAsyncHandleWrite #define redisAsyncHandleTimeout redictAsyncHandleTimeout #define redisAsyncRead redictAsyncRead #define redisAsyncWrite redictAsyncWrite /* Command functions for an async context. Write the command to the * output buffer and register the provided callback. */ #define redisvAsyncCommand redictvAsyncCommand #define redisAsyncCommand redictAsyncCommand #define redisAsyncCommandArgv redictAsyncCommandArgv #define redisAsyncFormattedCommand redictAsyncFormattedCommand #endif hiredict/shim/hiredis.h000066400000000000000000000167661460333451400154340ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2024 Hiredict Contributors * * SPDX-License-Identifier: BSD-3-Clause * SPDX-License-Identifier: LGPL-3.0-or-later * */ #ifndef __HIREDIS_H #define __HIREDIS_H #include #include #include #include #define HIREDIS_MAJOR HIREDICT_MAJOR #define HIREDIS_MINOR HIREDICT_MINOR #define HIREDIS_PATCH HIREDICT_PATCH #define HIREDIS_SONAME HIREDICT_MAJOR /* Connection type can be blocking or non-blocking and is set in the * least significant bit of the flags field in redictContext. */ #define REDIS_BLOCK REDICT_BLOCK /* Connection may be disconnected before being free'd. The second bit * in the flags field is set when the context is connected. */ #define REDIS_CONNECTED REDICT_CONNECTED /* The async API might try to disconnect cleanly and flush the output * buffer and read all subsequent replies before disconnecting. * This flag means no new commands can come in and the connection * should be terminated once all replies have been read. */ #define REDIS_DICONNECTING REDICT_DISCONNECTING /* Flag specific to the async API which means that the context should be clean * up as soon as possible. */ #define REDIS_FREEING REDICT_FREEING /* Flag that is set when an async callback is executed. */ #define REDIS_IN_CALLBACK REDICT_IN_CALLBACK /* Flag that is set when the async context has one or more subscriptions. */ #define REDIS_SUBSCRIBED REDICT_SUBSCRIBED /* Flag that is set when monitor mode is active */ #define REDIS_MONITORING REDICT_MONITORING /* Flag that is set when we should set SO_REUSEADDR before calling bind() */ #define REDIS_REUSEADDR REDICT_REUSEADDR /* Flag that is set when the async connection supports push replies. */ #define REDIS_SUPPORTS_PUSH REDICT_SUPPORTS_PUSH /** * Flag that indicates the user does not want the context to * be automatically freed upon error */ #define REDIS_NO_AUTO_FREE REDICT_NO_AUTO_FREE /* Flag that indicates the user does not want replies to be automatically freed */ #define REDIS_NO_AUTO_FREE_REPLIES REDICT_NO_AUTO_FREE_REPLIES /* Flags to prefer IPv6 or IPv4 when doing DNS lookup. (If both are set, * AF_UNSPEC is used.) */ #define REDIS_PREFER_IPV4 REDICT_PREFER_IPV4 #define REDIS_PREFER_IPV6 REDICT_PREFER_IPV6 #define REDIS_KEEPALIVE_INTERVAL REDICT_KEEPALIVE_INTERVAL /* seconds */ /* number of times we retry to connect in the case of EADDRNOTAVAIL and * SO_REUSEADDR is being used. */ #define REDIS_CONNECT_RETRIES REDICT_CONNECT_RETRIES /* RESP3 push helpers and callback prototypes */ #define redisIsPushReply(r) redictIsPushReply(r) #define redisPushFn redictPushFn #define redisAsyncPushFn redictAsyncPushFn /* This is the reply object returned by redistCommand() */ #define redisReply redictReply #define redisReaderCreate redictReaderCreate /* Functions to format a command according to the protocol. */ #define redisvFormatCommand redictvFormatCommand #define redisFormatCommand redictFormatCommand #define redisFormatCommandArgv redictFormatCommandArgv #define redisFormatSdsCommandArgv redictFormatSdsCommandArgv #define redisFreeCommand redictFreeCommand #define redisFreeSdsCommand redictFreeSdsCommand #define redisConnectionType redictConnectionType #define redisSsl redictSsl #define REDIS_OPT_NONBLOCK REDICT_OPT_NONBLOCK #define REDIS_OPT_REUSEADDR REDICT_OPT_REUSEADDR #define REDIS_OPT_NOAUTOFREE REDICT_OPT_NOAUTOFREE /* Don't automatically free the async * object on a connection failure, or * other implicit conditions. Only free * on an explicit call to disconnect() * or free() */ #define REDIS_OPT_NO_PUSH_AUTOFREE REDICT_OPT_NO_PUSH_AUTOFREE /* Don't automatically intercept and * free RESP3 PUSH replies. */ #define REDIS_OPT_NOAUTOFREEREPLIES REDICT_OPT_NOAUTOFREEREPLIES /* Don't automatically free replies. */ #define REDIS_OPT_PREFER_IPV4 REDICT_OPT_PREFER_IPV4 /* Prefer IPv4 in DNS lookups. */ #define REDIS_OPT_PREFER_IPV6 REDICT_OPT_PREFER_IPV6 /* Prefer IPv6 in DNS lookups. */ #define REDIS_OPT_PREFER_IP_UNSPEC REDICT_OPT_PREFER_IP_UNSPEC /* In Unix systems a file descriptor is a regular signed int, with -1 * representing an invalid descriptor. In Windows it is a SOCKET * (32- or 64-bit unsigned integer depending on the architecture), where * all bits set (~0) is INVALID_SOCKET. */ #define redisFD redictFD #define REDIS_INVALID_FD REDICT_INVALID_FD #define redisOptions redictOptions /** * Helper macros to initialize options to their specified fields. */ #define REDIS_OPTIONS_SET_TCP(opts, ip_, port_) REDICT_OPTIONS_SET_TCP(opts, ip_, port_) #define REDIS_OPTIONS_SET_UNIX(opts, path) REDICT_OPTIONS_SET_UNIX(opts, path) #define REDIS_OPTIONS_SET_PRIVDATA(opts, data, dtor) REDICT_OPTIONS_SET_PRIVDATA(opts, data, dtor) #define redisContextFuncs redictContextFuncs /* Context for a connection to Redict */ #define redisContext redictContext #define redisConnectWithOptions redictConnectWithOptions #define redisConnect redictConnect #define redisConnectWithTimeout redictConnectWithTimeout #define redisConnectNonBlock redictConnectNonBlock #define redisConnectBindNonBlock redictConnectBindNonBlock #define redisConnectBindNonBlockWithReuse redictConnectBindNonBlockWithReuse #define redisConnectUnix redictConnectUnix #define redisConnectUnixWithTimeout redictConnectUnixWithTimeout #define redisConnectUnixNonBlock redictConnectUnixNonBlock #define redisConnectFd redictConnectFd /** * Reconnect the given context using the saved information. * * This re-uses the exact same connect options as in the initial connection. * host, ip (or path), timeout and bind address are reused, * flags are used unmodified from the existing context. * * Returns REDICT_OK on successful connect or REDICT_ERR otherwise. */ #define redisReconnect redictReconnect #define redisSetPushCallback redictSetPushCallback #define redisSetTimeout redictSetTimeout #define redisEnableKeepAlive redictEnableKeepAlive #define redisEnableKeepAliveWithInterval redictEnableKeepAliveWithInterval #define redisSetTcpUserTimeout redictSetTcpUserTimeout #define redisFree redictFree #define redisFreeKeepFd redictFreeKeepFd #define redisBufferRead redictBufferRead #define redisBufferWrite redictBufferWrite /* In a blocking context, this function first checks if there are unconsumed * replies to return and returns one if so. Otherwise, it flushes the output * buffer to the socket and reads until it has a reply. In a non-blocking * context, it will return unconsumed replies until there are no more. */ #define redisGetReply redictGetReply #define redisGetReplyFromReader redictGetReplyFromReader /* Write a formatted command to the output buffer. Use these functions in blocking mode * to get a pipeline of commands. */ #define redisAppendFormattedCommand redictAppendFormattedCommand /* Write a command to the output buffer. Use these functions in blocking mode * to get a pipeline of commands. */ #define redisvAppendCommand redictvAppendCommand #define redisAppendCommand redictAppendCommand #define redisAppendCommandArgv redictAppendCommandArgv /* Issue a command to Redict. In a blocking context, it is identical to calling * redictAppendCommand, followed by redictGetReply. The function will return * NULL if there was an error in performing the request, otherwise it will * return the reply. In a non-blocking context, it is identical to calling * only redictAppendCommand and will always return NULL. */ #define redisvCommand redictvCommand #define redisCommand redictCommand #define redisCommandArgv redictCommandArgv #endif hiredict/shim/hiredis.pc.in000066400000000000000000000005731460333451400162010ustar00rootroot00000000000000prefix=@PREFIX@ install_libdir=@LIBDIR@ exec_prefix=${prefix} libdir=${exec_prefix}/${install_libdir} includedir=${prefix}/include pkgincludedir=${includedir}/hiredis Name: hiredis Description: Minimalistic C client library for Redict, compatibility shim. Version: @PROJECT_VERSION@ Libs: -L${libdir} -lhiredict Cflags: -I${pkgincludedir} -I${includedir} -D_FILE_OFFSET_BITS=64 hiredict/shim/hiredis_ssl.h000066400000000000000000000105621460333451400163010ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2024 Hiredict Contributors * * SPDX-License-Identifier: BSD-3-Clause * SPDX-License-Identifier: LGPL-3.0-or-later * */ #ifndef __HIREDIS_SSL_H #define __HIREDIS_SSL_H /* A wrapper around OpenSSL SSL_CTX to allow easy SSL use without directly * calling OpenSSL. */ #define redisSSLContext redictSSLContext /** #define redisCreateSSLContext redictCreateSSLContext */ #define redisSSLContextError redictSSLContextError #define REDIS_SSL_CTX_NONE REDICT_SSL_CTX_NONE /* No Error */ #define REDIS_SSL_CTX_CREATE_FAILED REDICT_SSL_CTX_CREATE_FAILED /* Failed to create OpenSSL SSL_CTX */ #define REDIS_SSL_CTX_CERT_KEY_REQUIRED REDICT_SSL_CTX_CERT_KEY_REQUIRED /* Client cert and key must both be specified or skipped */ #define REDIS_SSL_CTX_CA_CERT_LOAD_FAILED REDICT_SSL_CTX_CA_CERT_LOAD_FAILED /* Failed to load CA Certificate or CA Path */ #define REDIS_SSL_CTX_CLIENT_CERT_LOAD_FAILED REDICT_SSL_CTX_CLIENT_CERT_LOAD_FAILED /* Failed to load client certificate */ #define REDIS_SSL_CTX_CLIENT_DEFAULT_CERT_FAILED REDICT_SSL_CTX_CLIENT_DEFAULT_CERT_FAILED /* Failed to set client default certificate directory */ #define REDIS_SSL_CTX_PRIVATE_KEY_LOAD_FAILED REDICT_SSL_CTX_PRIVATE_KEY_LOAD_FAILED /* Failed to load private key */ #define REDIS_SSL_CTX_OS_CERTSTORE_OPEN_FAILED REDICT_SSL_CTX_OS_CERTSTORE_OPEN_FAILED /* Failed to open system certificate store */ #define REDIS_SSL_CTX_OS_CERT_ADD_FAILED REDICT_SSL_CTX_OS_CERT_ADD_FAILED /* Failed to add CA certificates obtained from system to the SSL context */ /* Constants that mirror OpenSSL's verify modes. By default, #define redisCreateSSLContext redictCreateSSLContext * Some Redict clients disable peer verification if there are no * certificates specified. */ #define REDIS_SSL_VERIFY_NONE REDICT_SSL_VERIFY_NONE #define REDIS_SSL_VERIFY_PEER REDICT_SSL_VERIFY_PEER #define REDIS_SSL_VERIFY_FAIL_IF_NO_PEER_CERT REDICT_SSL_VERIFY_FAIL_IF_NO_PEER_CERT #define REDIS_SSL_VERIFY_CLIENT_ONCE REDICT_SSL_VERIFY_CLIENT_ONCE #define REDIS_SSL_VERIFY_POST_HANDSHAKE REDICT_SSL_VERIFY_POST_HANDSHAKE /* Options to create an OpenSSL context. */ #define redisSSLOptions redictSSLOptions /** * Return the error message corresponding with the specified error code. */ #define redisSSLContextGetError redictSSLContextGetError /** * Helper function to initialize the OpenSSL library. * * OpenSSL requires one-time initialization before it can be used. Callers should * call this function only once, and only if OpenSSL is not directly initialized * elsewhere. */ #define redisInitOpenSSL redictInitOpenSSL /** * Helper function to initialize an OpenSSL context that can be used * to initiate SSL connections. * * cacert_filename is an optional name of a CA certificate/bundle file to load * and use for validation. * * capath is an optional directory path where trusted CA certificate files are * stored in an OpenSSL-compatible structure. * * cert_filename and private_key_filename are optional names of a client side * certificate and private key files to use for authentication. They need to * be both specified or omitted. * * server_name is an optional and will be used as a server name indication * (SNI) TLS extension. * * If error is non-null, it will be populated in case the context creation fails * (returning a NULL). */ #define redisCreateSSLContext redictCreateSSLContext /** * Helper function to initialize an OpenSSL context that can be used #define redisCreateSSLContext redictCreateSSLContext * * options contains a structure of SSL options to use. * * If error is non-null, it will be populated in case the context creation fails * (returning a NULL). */ #define redisCreateSSLContextWithOptions redictCreateSSLContextWithOptions /** * Free a previously created OpenSSL context. */ #define redisFreeSSLContext redictFreeSSLContext /** * Initiate SSL on an existing redictContext. * #define redisInitiateSSL redictInitiateSSL * to directly interact with OpenSSL, and instead uses a redictSSLContext #define redisCreateSSLContext redictCreateSSLContext */ #define redisInitiateSSLWithContext redictInitiateSSLWithContext /** * Initiate SSL/TLS negotiation on a provided OpenSSL SSL object. */ #define redisInitiateSSL redictInitiateSSL #endif /* __HIREDICT_SSL_H */ hiredict/shim/hiredis_ssl.pc.in000066400000000000000000000005401460333451400170540ustar00rootroot00000000000000prefix=@PREFIX@ install_libdir=@LIBDIR@ exec_prefix=${prefix} libdir=${exec_prefix}/${install_libdir} includedir=${prefix}/include pkgincludedir=${includedir}/hiredis Name: hiredis_ssl Description: SSL Support for hiredict, compatibility shim. Version: @PROJECT_VERSION@ Requires: hiredis Libs: -L${libdir} -lhiredict_ssl Libs.private: -lssl -lcrypto hiredict/shim/read.h000066400000000000000000000044551460333451400147100ustar00rootroot00000000000000/* * * SPDX-FileCopyrightText: 2024 Hiredict Contributors * * SPDX-License-Identifier: BSD-3-Clause * SPDX-License-Identifier: LGPL-3.0-or-later * */ #ifndef __HIREDIS_READ_H #define __HIREDIS_READ_H #include #define REDIS_ERR REDICT_ERR #define REDIS_OK REDICT_OK /* When an error occurs, the err flag in a context is set to hold the type of * error that occurred. REDICT_ERR_IO means there was an I/O error and you * should use the "errno" variable to find out what is wrong. * For other values, the "errstr" field will hold a description. */ #define REDIS_ERR_IO REDICT_ERR_IO /* Error in read or write */ #define REDIS_ERR_EOF REDICT_ERR_EOF /* End of file */ #define REDIS_ERR_PROTOCOL REDICT_ERR_PROTOCOL /* Protocol error */ #define REDIS_ERR_OOM REDICT_ERR_OOM /* Out of memory */ #define REDIS_ERR_TIMEOUT REDICT_ERR_TIMEOUT /* Timed out */ #define REDIS_ERR_OTHER REDICT_ERR_OTHER /* Everything else... */ #define REDIS_REPLY_STRING REDICT_REPLY_STRING #define REDIS_REPLY_ARRAY REDICT_REPLY_ARRAY #define REDIS_REPLY_INTEGER REDICT_REPLY_INTEGER #define REDIS_REPLY_NIL REDICT_REPLY_NIL #define REDIS_REPLY_STATUS REDICT_REPLY_STATUS #define REDIS_REPLY_ERROR REDICT_REPLY_ERROR #define REDIS_REPLY_DOUBLE REDICT_REPLY_DOUBLE #define REDIS_REPLY_BOOL REDICT_REPLY_BOOL #define REDIS_REPLY_MAP REDICT_REPLY_MAP #define REDIS_REPLY_SET REDICT_REPLY_SET #define REDIS_REPLY_ATTR REDICT_REPLY_ATTR #define REDIS_REPLY_PUSH REDICT_REPLY_PUSH #define REDIS_REPLY_BIGNUM REDICT_REPLY_BIGNUM #define REDIS_REPLY_VERB REDICT_REPLY_VERB /* Default max unused reader buffer. */ #define REDIS_READER_MAX_BUF REDICT_READER_MAX_BUF /* Default multi-bulk element limit */ #define REDIS_READER_MAX_ARRAY_ELEMENTS REDICT_READER_MAX_ARRAY_ELEMENTS #define redisReadTask redictReadTask #define redisReplyObjectFunctions redictReplyObjectFunctions #define redisReader redictReader /* Public API for the protocol parser. */ #define redisReaderCreateWithFunctions redictReaderCreateWithFunctions #define redisReaderFree redictReaderFree #define redisReaderFeed redictReaderFeed #define redisReaderGetReply redictReaderGetReply #define redisReaderSetPrivdata(_r, _p) redictReaderSetPrivdata(_r, _p) #define redisReaderGetObject(_r) redictReaderGetObject(_r) #define redisReaderGetError(_r) redictReaderGetError(_r) #endif hiredict/shim/sds.h000066400000000000000000000003361460333451400145600ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: 2024 Hiredict Contributors * * SPDX-License-Identifier: BSD-3-Clause * SPDX-License-Identifier: LGPL-3.0-or-later * */ #ifndef __SDS_H #define __SDS_H #include #endif hiredict/shim/sockcompat.h000066400000000000000000000006541460333451400161350ustar00rootroot00000000000000/* * Copyright (c) 2019, Marcus Geelnard * * SPDX-FileCopyrightText: 2024 Hiredict Contributors * SPDX-FileCopyrightText: 2024 Marcus Geelnard * * SPDX-License-Identifier: BSD-3-Clause * SPDX-License-Identifier: LGPL-3.0-or-later * */ #ifndef __HIREDIS_SOCKCOMPAT_H #define __HIREDIS_SOCKCOMPAT_H #include #endif /* __HIREDIS_SOCKCOMPAT_H */ hiredict/sockcompat.c000066400000000000000000000206101460333451400151620ustar00rootroot00000000000000/* * Copyright (c) 2019, Marcus Geelnard * * SPDX-FileCopyrightText: 2024 Hiredict Contributors * SPDX-FileCopyrightText: 2024 Marcus Geelnard * * SPDX-License-Identifier: BSD-3-Clause * SPDX-License-Identifier: LGPL-3.0-or-later * */ #define REDICT_SOCKCOMPAT_IMPLEMENTATION #include "sockcompat.h" #ifdef _WIN32 static int _wsaErrorToErrno(int err) { switch (err) { case WSAEWOULDBLOCK: return EWOULDBLOCK; case WSAEINPROGRESS: return EINPROGRESS; case WSAEALREADY: return EALREADY; case WSAENOTSOCK: return ENOTSOCK; case WSAEDESTADDRREQ: return EDESTADDRREQ; case WSAEMSGSIZE: return EMSGSIZE; case WSAEPROTOTYPE: return EPROTOTYPE; case WSAENOPROTOOPT: return ENOPROTOOPT; case WSAEPROTONOSUPPORT: return EPROTONOSUPPORT; case WSAEOPNOTSUPP: return EOPNOTSUPP; case WSAEAFNOSUPPORT: return EAFNOSUPPORT; case WSAEADDRINUSE: return EADDRINUSE; case WSAEADDRNOTAVAIL: return EADDRNOTAVAIL; case WSAENETDOWN: return ENETDOWN; case WSAENETUNREACH: return ENETUNREACH; case WSAENETRESET: return ENETRESET; case WSAECONNABORTED: return ECONNABORTED; case WSAECONNRESET: return ECONNRESET; case WSAENOBUFS: return ENOBUFS; case WSAEISCONN: return EISCONN; case WSAENOTCONN: return ENOTCONN; case WSAETIMEDOUT: return ETIMEDOUT; case WSAECONNREFUSED: return ECONNREFUSED; case WSAELOOP: return ELOOP; case WSAENAMETOOLONG: return ENAMETOOLONG; case WSAEHOSTUNREACH: return EHOSTUNREACH; case WSAENOTEMPTY: return ENOTEMPTY; default: /* We just return a generic I/O error if we could not find a relevant error. */ return EIO; } } static void _updateErrno(int success) { errno = success ? 0 : _wsaErrorToErrno(WSAGetLastError()); } static int _initWinsock() { static int s_initialized = 0; if (!s_initialized) { static WSADATA wsadata; int err = WSAStartup(MAKEWORD(2,2), &wsadata); if (err != 0) { errno = _wsaErrorToErrno(err); return 0; } s_initialized = 1; } return 1; } int win32_getaddrinfo(const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res) { /* Note: This function is likely to be called before other functions, so run init here. */ if (!_initWinsock()) { return EAI_FAIL; } switch (getaddrinfo(node, service, hints, res)) { case 0: return 0; case WSATRY_AGAIN: return EAI_AGAIN; case WSAEINVAL: return EAI_BADFLAGS; case WSAEAFNOSUPPORT: return EAI_FAMILY; case WSA_NOT_ENOUGH_MEMORY: return EAI_MEMORY; case WSAHOST_NOT_FOUND: return EAI_NONAME; case WSATYPE_NOT_FOUND: return EAI_SERVICE; case WSAESOCKTNOSUPPORT: return EAI_SOCKTYPE; default: return EAI_FAIL; /* Including WSANO_RECOVERY */ } } const char *win32_gai_strerror(int errcode) { switch (errcode) { case 0: errcode = 0; break; case EAI_AGAIN: errcode = WSATRY_AGAIN; break; case EAI_BADFLAGS: errcode = WSAEINVAL; break; case EAI_FAMILY: errcode = WSAEAFNOSUPPORT; break; case EAI_MEMORY: errcode = WSA_NOT_ENOUGH_MEMORY; break; case EAI_NONAME: errcode = WSAHOST_NOT_FOUND; break; case EAI_SERVICE: errcode = WSATYPE_NOT_FOUND; break; case EAI_SOCKTYPE: errcode = WSAESOCKTNOSUPPORT; break; default: errcode = WSANO_RECOVERY; break; /* Including EAI_FAIL */ } return gai_strerror(errcode); } void win32_freeaddrinfo(struct addrinfo *res) { freeaddrinfo(res); } SOCKET win32_socket(int domain, int type, int protocol) { SOCKET s; /* Note: This function is likely to be called before other functions, so run init here. */ if (!_initWinsock()) { return INVALID_SOCKET; } _updateErrno((s = socket(domain, type, protocol)) != INVALID_SOCKET); return s; } int win32_ioctl(SOCKET fd, unsigned long request, unsigned long *argp) { int ret = ioctlsocket(fd, (long)request, argp); _updateErrno(ret != SOCKET_ERROR); return ret != SOCKET_ERROR ? ret : -1; } int win32_bind(SOCKET sockfd, const struct sockaddr *addr, socklen_t addrlen) { int ret = bind(sockfd, addr, addrlen); _updateErrno(ret != SOCKET_ERROR); return ret != SOCKET_ERROR ? ret : -1; } int win32_connect(SOCKET sockfd, const struct sockaddr *addr, socklen_t addrlen) { int ret = connect(sockfd, addr, addrlen); _updateErrno(ret != SOCKET_ERROR); /* For Winsock connect(), the WSAEWOULDBLOCK error means the same thing as * EINPROGRESS for POSIX connect(), so we do that translation to keep POSIX * logic consistent. * Additionally, WSAALREADY is can be reported as WSAEINVAL to and this is * translated to EIO. Convert appropriately */ int err = errno; if (err == EWOULDBLOCK) { errno = EINPROGRESS; } else if (err == EIO) { errno = EALREADY; } return ret != SOCKET_ERROR ? ret : -1; } int win32_getsockopt(SOCKET sockfd, int level, int optname, void *optval, socklen_t *optlen) { int ret = 0; if ((level == SOL_SOCKET) && ((optname == SO_RCVTIMEO) || (optname == SO_SNDTIMEO))) { if (*optlen >= sizeof (struct timeval)) { struct timeval *tv = optval; DWORD timeout = 0; socklen_t dwlen = 0; ret = getsockopt(sockfd, level, optname, (char *)&timeout, &dwlen); tv->tv_sec = timeout / 1000; tv->tv_usec = (timeout * 1000) % 1000000; } else { ret = WSAEFAULT; } *optlen = sizeof (struct timeval); } else { ret = getsockopt(sockfd, level, optname, (char*)optval, optlen); } if (ret != SOCKET_ERROR && level == SOL_SOCKET && optname == SO_ERROR) { /* translate SO_ERROR codes, if non-zero */ int err = *(int*)optval; if (err != 0) { err = _wsaErrorToErrno(err); *(int*)optval = err; } } _updateErrno(ret != SOCKET_ERROR); return ret != SOCKET_ERROR ? ret : -1; } int win32_setsockopt(SOCKET sockfd, int level, int optname, const void *optval, socklen_t optlen) { int ret = 0; if ((level == SOL_SOCKET) && ((optname == SO_RCVTIMEO) || (optname == SO_SNDTIMEO))) { const struct timeval *tv = optval; DWORD timeout = tv->tv_sec * 1000 + tv->tv_usec / 1000; ret = setsockopt(sockfd, level, optname, (const char*)&timeout, sizeof(DWORD)); } else { ret = setsockopt(sockfd, level, optname, (const char*)optval, optlen); } _updateErrno(ret != SOCKET_ERROR); return ret != SOCKET_ERROR ? ret : -1; } int win32_close(SOCKET fd) { int ret = closesocket(fd); _updateErrno(ret != SOCKET_ERROR); return ret != SOCKET_ERROR ? ret : -1; } ssize_t win32_recv(SOCKET sockfd, void *buf, size_t len, int flags) { int ret = recv(sockfd, (char*)buf, (int)len, flags); _updateErrno(ret != SOCKET_ERROR); return ret != SOCKET_ERROR ? ret : -1; } ssize_t win32_send(SOCKET sockfd, const void *buf, size_t len, int flags) { int ret = send(sockfd, (const char*)buf, (int)len, flags); _updateErrno(ret != SOCKET_ERROR); return ret != SOCKET_ERROR ? ret : -1; } int win32_poll(struct pollfd *fds, nfds_t nfds, int timeout) { int ret = WSAPoll(fds, nfds, timeout); _updateErrno(ret != SOCKET_ERROR); return ret != SOCKET_ERROR ? ret : -1; } int win32_redictKeepAlive(SOCKET sockfd, int interval_ms) { struct tcp_keepalive cfg; DWORD bytes_in; int res; cfg.onoff = 1; cfg.keepaliveinterval = interval_ms; cfg.keepalivetime = interval_ms; res = WSAIoctl(sockfd, SIO_KEEPALIVE_VALS, &cfg, sizeof(struct tcp_keepalive), NULL, 0, &bytes_in, NULL, NULL); return res == 0 ? 0 : _wsaErrorToErrno(res); } #endif /* _WIN32 */ hiredict/sockcompat.h000066400000000000000000000060411460333451400151710ustar00rootroot00000000000000/* * Copyright (c) 2019, Marcus Geelnard * * SPDX-FileCopyrightText: 2024 Hiredict Contributors * SPDX-FileCopyrightText: 2024 Marcus Geelnard * * SPDX-License-Identifier: BSD-3-Clause * SPDX-License-Identifier: LGPL-3.0-or-later * */ #ifndef __SOCKCOMPAT_H #define __SOCKCOMPAT_H #ifndef _WIN32 /* For POSIX systems we use the standard BSD socket API. */ #include #include #include #include #include #include #include #include #include #else /* For Windows we use winsock. */ #undef _WIN32_WINNT #define _WIN32_WINNT 0x0600 /* To get WSAPoll etc. */ #include #include #include #include #include #ifdef _MSC_VER typedef long long ssize_t; #endif /* Emulate the parts of the BSD socket API that we need (override the winsock signatures). */ int win32_getaddrinfo(const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res); const char *win32_gai_strerror(int errcode); void win32_freeaddrinfo(struct addrinfo *res); SOCKET win32_socket(int domain, int type, int protocol); int win32_ioctl(SOCKET fd, unsigned long request, unsigned long *argp); int win32_bind(SOCKET sockfd, const struct sockaddr *addr, socklen_t addrlen); int win32_connect(SOCKET sockfd, const struct sockaddr *addr, socklen_t addrlen); int win32_getsockopt(SOCKET sockfd, int level, int optname, void *optval, socklen_t *optlen); int win32_setsockopt(SOCKET sockfd, int level, int optname, const void *optval, socklen_t optlen); int win32_close(SOCKET fd); ssize_t win32_recv(SOCKET sockfd, void *buf, size_t len, int flags); ssize_t win32_send(SOCKET sockfd, const void *buf, size_t len, int flags); typedef ULONG nfds_t; int win32_poll(struct pollfd *fds, nfds_t nfds, int timeout); int win32_redictKeepAlive(SOCKET sockfd, int interval_ms); #ifndef REDICT_SOCKCOMPAT_IMPLEMENTATION #define getaddrinfo(node, service, hints, res) win32_getaddrinfo(node, service, hints, res) #undef gai_strerror #define gai_strerror(errcode) win32_gai_strerror(errcode) #define freeaddrinfo(res) win32_freeaddrinfo(res) #define socket(domain, type, protocol) win32_socket(domain, type, protocol) #define ioctl(fd, request, argp) win32_ioctl(fd, request, argp) #define bind(sockfd, addr, addrlen) win32_bind(sockfd, addr, addrlen) #define connect(sockfd, addr, addrlen) win32_connect(sockfd, addr, addrlen) #define getsockopt(sockfd, level, optname, optval, optlen) win32_getsockopt(sockfd, level, optname, optval, optlen) #define setsockopt(sockfd, level, optname, optval, optlen) win32_setsockopt(sockfd, level, optname, optval, optlen) #define close(fd) win32_close(fd) #define recv(sockfd, buf, len, flags) win32_recv(sockfd, buf, len, flags) #define send(sockfd, buf, len, flags) win32_send(sockfd, buf, len, flags) #define poll(fds, nfds, timeout) win32_poll(fds, nfds, timeout) #endif /* REDICT_SOCKCOMPAT_IMPLEMENTATION */ #endif /* _WIN32 */ #endif /* __SOCKCOMPAT_H */ hiredict/ssl.c000066400000000000000000000400521460333451400136220ustar00rootroot00000000000000/* * Copyright (c) 2009-2011, Salvatore Sanfilippo * Copyright (c) 2010-2011, Pieter Noordhuis * Copyright (c) 2019, Redis Labs * * SPDX-FileCopyrightText: 2024 Hiredict Contributors * SPDX-FileCopyrightText: 2024 Salvatore Sanfilippo * SPDX-FileCopyrightText: 2024 Pieter Noordhuis * SPDX-FileCopyrightText: 2024 Redis Labs * * SPDX-License-Identifier: BSD-3-Clause * SPDX-License-Identifier: LGPL-3.0-or-later * */ #include "hiredict.h" #include "async.h" #include "net.h" #include #include #include #ifdef _WIN32 #include #include #ifdef OPENSSL_IS_BORINGSSL #undef X509_NAME #undef X509_EXTENSIONS #undef PKCS7_ISSUER_AND_SERIAL #undef PKCS7_SIGNER_INFO #undef OCSP_REQUEST #undef OCSP_RESPONSE #endif #else #include #endif #include #include #include "win32.h" #include "async_private.h" #include "hiredict_ssl.h" #define OPENSSL_1_1_0 0x10100000L void __redictSetError(redictContext *c, int type, const char *str); struct redictSSLContext { /* Associated OpenSSL SSL_CTX as created by redictCreateSSLContext() */ SSL_CTX *ssl_ctx; /* Requested SNI, or NULL */ char *server_name; }; /* The SSL connection context is attached to SSL/TLS connections as a privdata. */ typedef struct redictSSL { /** * OpenSSL SSL object. */ SSL *ssl; /** * SSL_write() requires to be called again with the same arguments it was * previously called with in the event of an SSL_read/SSL_write situation */ size_t lastLen; /** Whether the SSL layer requires read (possibly before a write) */ int wantRead; /** * Whether a write was requested prior to a read. If set, the write() * should resume whenever a read takes place, if possible */ int pendingWrite; } redictSSL; /* Forward declaration */ redictContextFuncs redictContextSSLFuncs; /** * OpenSSL global initialization and locking handling callbacks. * Note that this is only required for OpenSSL < 1.1.0. */ #if OPENSSL_VERSION_NUMBER < OPENSSL_1_1_0 #define HIREDICT_USE_CRYPTO_LOCKS #endif #ifdef HIREDICT_USE_CRYPTO_LOCKS #ifdef _WIN32 typedef CRITICAL_SECTION sslLockType; static void sslLockInit(sslLockType* l) { InitializeCriticalSection(l); } static void sslLockAcquire(sslLockType* l) { EnterCriticalSection(l); } static void sslLockRelease(sslLockType* l) { LeaveCriticalSection(l); } #else typedef pthread_mutex_t sslLockType; static void sslLockInit(sslLockType *l) { pthread_mutex_init(l, NULL); } static void sslLockAcquire(sslLockType *l) { pthread_mutex_lock(l); } static void sslLockRelease(sslLockType *l) { pthread_mutex_unlock(l); } #endif static sslLockType* ossl_locks; static void opensslDoLock(int mode, int lkid, const char *f, int line) { sslLockType *l = ossl_locks + lkid; if (mode & CRYPTO_LOCK) { sslLockAcquire(l); } else { sslLockRelease(l); } (void)f; (void)line; } static int initOpensslLocks(void) { unsigned ii, nlocks; if (CRYPTO_get_locking_callback() != NULL) { /* Someone already set the callback before us. Don't destroy it! */ return REDICT_OK; } nlocks = CRYPTO_num_locks(); ossl_locks = hi_malloc(sizeof(*ossl_locks) * nlocks); if (ossl_locks == NULL) return REDICT_ERR; for (ii = 0; ii < nlocks; ii++) { sslLockInit(ossl_locks + ii); } CRYPTO_set_locking_callback(opensslDoLock); return REDICT_OK; } #endif /* HIREDICT_USE_CRYPTO_LOCKS */ int redictInitOpenSSL(void) { SSL_library_init(); #ifdef HIREDICT_USE_CRYPTO_LOCKS initOpensslLocks(); #endif return REDICT_OK; } /** * redictSSLContext helper context destruction. */ const char *redictSSLContextGetError(redictSSLContextError error) { switch (error) { case REDICT_SSL_CTX_NONE: return "No Error"; case REDICT_SSL_CTX_CREATE_FAILED: return "Failed to create OpenSSL SSL_CTX"; case REDICT_SSL_CTX_CERT_KEY_REQUIRED: return "Client cert and key must both be specified or skipped"; case REDICT_SSL_CTX_CA_CERT_LOAD_FAILED: return "Failed to load CA Certificate or CA Path"; case REDICT_SSL_CTX_CLIENT_CERT_LOAD_FAILED: return "Failed to load client certificate"; case REDICT_SSL_CTX_PRIVATE_KEY_LOAD_FAILED: return "Failed to load private key"; case REDICT_SSL_CTX_OS_CERTSTORE_OPEN_FAILED: return "Failed to open system certificate store"; case REDICT_SSL_CTX_OS_CERT_ADD_FAILED: return "Failed to add CA certificates obtained from system to the SSL context"; default: return "Unknown error code"; } } void redictFreeSSLContext(redictSSLContext *ctx) { if (!ctx) return; if (ctx->server_name) { hi_free(ctx->server_name); ctx->server_name = NULL; } if (ctx->ssl_ctx) { SSL_CTX_free(ctx->ssl_ctx); ctx->ssl_ctx = NULL; } hi_free(ctx); } /** * redictSSLContext helper context initialization. */ redictSSLContext *redictCreateSSLContext(const char *cacert_filename, const char *capath, const char *cert_filename, const char *private_key_filename, const char *server_name, redictSSLContextError *error) { redictSSLOptions options = { .cacert_filename = cacert_filename, .capath = capath, .cert_filename = cert_filename, .private_key_filename = private_key_filename, .server_name = server_name, .verify_mode = REDICT_SSL_VERIFY_PEER, }; return redictCreateSSLContextWithOptions(&options, error); } redictSSLContext *redictCreateSSLContextWithOptions(redictSSLOptions *options, redictSSLContextError *error) { const char *cacert_filename = options->cacert_filename; const char *capath = options->capath; const char *cert_filename = options->cert_filename; const char *private_key_filename = options->private_key_filename; const char *server_name = options->server_name; #ifdef _WIN32 HCERTSTORE win_store = NULL; PCCERT_CONTEXT win_ctx = NULL; #endif redictSSLContext *ctx = hi_calloc(1, sizeof(redictSSLContext)); if (ctx == NULL) goto error; const SSL_METHOD *ssl_method; #if OPENSSL_VERSION_NUMBER >= OPENSSL_1_1_0 ssl_method = TLS_client_method(); #else ssl_method = SSLv23_client_method(); #endif ctx->ssl_ctx = SSL_CTX_new(ssl_method); if (!ctx->ssl_ctx) { if (error) *error = REDICT_SSL_CTX_CREATE_FAILED; goto error; } #if OPENSSL_VERSION_NUMBER >= OPENSSL_1_1_0 SSL_CTX_set_min_proto_version(ctx->ssl_ctx, TLS1_2_VERSION); #else SSL_CTX_set_options(ctx->ssl_ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1); #endif SSL_CTX_set_verify(ctx->ssl_ctx, options->verify_mode, NULL); if ((cert_filename != NULL && private_key_filename == NULL) || (private_key_filename != NULL && cert_filename == NULL)) { if (error) *error = REDICT_SSL_CTX_CERT_KEY_REQUIRED; goto error; } if (capath || cacert_filename) { #ifdef _WIN32 if (0 == strcmp(cacert_filename, "wincert")) { win_store = CertOpenSystemStore(NULL, "Root"); if (!win_store) { if (error) *error = REDICT_SSL_CTX_OS_CERTSTORE_OPEN_FAILED; goto error; } X509_STORE* store = SSL_CTX_get_cert_store(ctx->ssl_ctx); while (win_ctx = CertEnumCertificatesInStore(win_store, win_ctx)) { X509* x509 = NULL; x509 = d2i_X509(NULL, (const unsigned char**)&win_ctx->pbCertEncoded, win_ctx->cbCertEncoded); if (x509) { if ((1 != X509_STORE_add_cert(store, x509)) || (1 != SSL_CTX_add_client_CA(ctx->ssl_ctx, x509))) { if (error) *error = REDICT_SSL_CTX_OS_CERT_ADD_FAILED; goto error; } X509_free(x509); } } CertFreeCertificateContext(win_ctx); CertCloseStore(win_store, 0); } else #endif if (!SSL_CTX_load_verify_locations(ctx->ssl_ctx, cacert_filename, capath)) { if (error) *error = REDICT_SSL_CTX_CA_CERT_LOAD_FAILED; goto error; } } else { if (!SSL_CTX_set_default_verify_paths(ctx->ssl_ctx)) { if (error) *error = REDICT_SSL_CTX_CLIENT_DEFAULT_CERT_FAILED; goto error; } } if (cert_filename) { if (!SSL_CTX_use_certificate_chain_file(ctx->ssl_ctx, cert_filename)) { if (error) *error = REDICT_SSL_CTX_CLIENT_CERT_LOAD_FAILED; goto error; } if (!SSL_CTX_use_PrivateKey_file(ctx->ssl_ctx, private_key_filename, SSL_FILETYPE_PEM)) { if (error) *error = REDICT_SSL_CTX_PRIVATE_KEY_LOAD_FAILED; goto error; } } if (server_name) ctx->server_name = hi_strdup(server_name); return ctx; error: #ifdef _WIN32 CertFreeCertificateContext(win_ctx); CertCloseStore(win_store, 0); #endif redictFreeSSLContext(ctx); return NULL; } /** * SSL Connection initialization. */ static int redictSSLConnect(redictContext *c, SSL *ssl) { if (c->privctx) { __redictSetError(c, REDICT_ERR_OTHER, "redictContext was already associated"); return REDICT_ERR; } redictSSL *rssl = hi_calloc(1, sizeof(redictSSL)); if (rssl == NULL) { __redictSetError(c, REDICT_ERR_OOM, "Out of memory"); return REDICT_ERR; } c->funcs = &redictContextSSLFuncs; rssl->ssl = ssl; SSL_set_mode(rssl->ssl, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); SSL_set_fd(rssl->ssl, c->fd); SSL_set_connect_state(rssl->ssl); ERR_clear_error(); int rv = SSL_connect(rssl->ssl); if (rv == 1) { c->privctx = rssl; return REDICT_OK; } rv = SSL_get_error(rssl->ssl, rv); if (((c->flags & REDICT_BLOCK) == 0) && (rv == SSL_ERROR_WANT_READ || rv == SSL_ERROR_WANT_WRITE)) { c->privctx = rssl; return REDICT_OK; } if (c->err == 0) { char err[512]; if (rv == SSL_ERROR_SYSCALL) snprintf(err,sizeof(err)-1,"SSL_connect failed: %s",strerror(errno)); else { unsigned long e = ERR_peek_last_error(); snprintf(err,sizeof(err)-1,"SSL_connect failed: %s", ERR_reason_error_string(e)); } __redictSetError(c, REDICT_ERR_IO, err); } hi_free(rssl); return REDICT_ERR; } /** * A wrapper around redictSSLConnect() for users who manage their own context and * create their own SSL object. */ int redictInitiateSSL(redictContext *c, SSL *ssl) { return redictSSLConnect(c, ssl); } /** * A wrapper around redictSSLConnect() for users who use redictSSLContext and don't * manage their own SSL objects. */ int redictInitiateSSLWithContext(redictContext *c, redictSSLContext *redict_ssl_ctx) { if (!c || !redict_ssl_ctx) return REDICT_ERR; /* We want to verify that redictSSLConnect() won't fail on this, as it will * not own the SSL object in that case and we'll end up leaking. */ if (c->privctx) return REDICT_ERR; SSL *ssl = SSL_new(redict_ssl_ctx->ssl_ctx); if (!ssl) { __redictSetError(c, REDICT_ERR_OTHER, "Couldn't create new SSL instance"); goto error; } if (redict_ssl_ctx->server_name) { if (!SSL_set_tlsext_host_name(ssl, redict_ssl_ctx->server_name)) { __redictSetError(c, REDICT_ERR_OTHER, "Failed to set server_name/SNI"); goto error; } } if (redictSSLConnect(c, ssl) != REDICT_OK) { goto error; } return REDICT_OK; error: if (ssl) SSL_free(ssl); return REDICT_ERR; } static int maybeCheckWant(redictSSL *rssl, int rv) { /** * If the error is WANT_READ or WANT_WRITE, the appropriate flags are set * and true is returned. False is returned otherwise */ if (rv == SSL_ERROR_WANT_READ) { rssl->wantRead = 1; return 1; } else if (rv == SSL_ERROR_WANT_WRITE) { rssl->pendingWrite = 1; return 1; } else { return 0; } } /** * Implementation of redictContextFuncs for SSL connections. */ static void redictSSLFree(void *privctx){ redictSSL *rsc = privctx; if (!rsc) return; if (rsc->ssl) { SSL_free(rsc->ssl); rsc->ssl = NULL; } hi_free(rsc); } static ssize_t redictSSLRead(redictContext *c, char *buf, size_t bufcap) { redictSSL *rssl = c->privctx; int nread = SSL_read(rssl->ssl, buf, bufcap); if (nread > 0) { return nread; } else if (nread == 0) { __redictSetError(c, REDICT_ERR_EOF, "Server closed the connection"); return -1; } else { int err = SSL_get_error(rssl->ssl, nread); if (c->flags & REDICT_BLOCK) { /** * In blocking mode, we should never end up in a situation where * we get an error without it being an actual error, except * in the case of EINTR, which can be spuriously received from * debuggers or whatever. */ if (errno == EINTR) { return 0; } else { const char *msg = NULL; if (errno == EAGAIN) { msg = "Resource temporarily unavailable"; } __redictSetError(c, REDICT_ERR_IO, msg); return -1; } } /** * We can very well get an EWOULDBLOCK/EAGAIN, however */ if (maybeCheckWant(rssl, err)) { return 0; } else { __redictSetError(c, REDICT_ERR_IO, NULL); return -1; } } } static ssize_t redictSSLWrite(redictContext *c) { redictSSL *rssl = c->privctx; size_t len = rssl->lastLen ? rssl->lastLen : sdslen(c->obuf); int rv = SSL_write(rssl->ssl, c->obuf, len); if (rv > 0) { rssl->lastLen = 0; } else if (rv < 0) { rssl->lastLen = len; int err = SSL_get_error(rssl->ssl, rv); if ((c->flags & REDICT_BLOCK) == 0 && maybeCheckWant(rssl, err)) { return 0; } else { __redictSetError(c, REDICT_ERR_IO, NULL); return -1; } } return rv; } static void redictSSLAsyncRead(redictAsyncContext *ac) { int rv; redictSSL *rssl = ac->c.privctx; redictContext *c = &ac->c; rssl->wantRead = 0; if (rssl->pendingWrite) { int done; /* This is probably just a write event */ rssl->pendingWrite = 0; rv = redictBufferWrite(c, &done); if (rv == REDICT_ERR) { __redictAsyncDisconnect(ac); return; } else if (!done) { _EL_ADD_WRITE(ac); } } rv = redictBufferRead(c); if (rv == REDICT_ERR) { __redictAsyncDisconnect(ac); } else { _EL_ADD_READ(ac); redictProcessCallbacks(ac); } } static void redictSSLAsyncWrite(redictAsyncContext *ac) { int rv, done = 0; redictSSL *rssl = ac->c.privctx; redictContext *c = &ac->c; rssl->pendingWrite = 0; rv = redictBufferWrite(c, &done); if (rv == REDICT_ERR) { __redictAsyncDisconnect(ac); return; } if (!done) { if (rssl->wantRead) { /* Need to read-before-write */ rssl->pendingWrite = 1; _EL_DEL_WRITE(ac); } else { /* No extra reads needed, just need to write more */ _EL_ADD_WRITE(ac); } } else { /* Already done! */ _EL_DEL_WRITE(ac); } /* Always reschedule a read */ _EL_ADD_READ(ac); } redictContextFuncs redictContextSSLFuncs = { .close = redictNetClose, .free_privctx = redictSSLFree, .async_read = redictSSLAsyncRead, .async_write = redictSSLAsyncWrite, .read = redictSSLRead, .write = redictSSLWrite }; hiredict/test.c000066400000000000000000002543761460333451400140200ustar00rootroot00000000000000/* * Copyright (c) 2009-2011, Salvatore Sanfilippo * * SPDX-FileCopyrightText: 2024 Hiredict Contributors * SPDX-FileCopyrightText: 2024 Salvatore Sanfilippo * * SPDX-License-Identifier: BSD-3-Clause * SPDX-License-Identifier: LGPL-3.0-or-later * */ #include "fmacros.h" #include "sockcompat.h" #include #include #include #ifndef _WIN32 #include #include #endif #include #include #include #include #include #include "hiredict.h" #include "async.h" #include "adapters/poll.h" #ifdef HIREDICT_TEST_SSL #include "hiredict_ssl.h" #endif #ifdef HIREDICT_TEST_ASYNC #include "adapters/libevent.h" #include #endif #include "net.h" #include "win32.h" enum connection_type { CONN_TCP, CONN_UNIX, CONN_FD, CONN_SSL }; struct config { enum connection_type type; struct timeval connect_timeout; struct { const char *host; int port; } tcp; struct { const char *path; } unix_sock; struct { const char *host; int port; const char *ca_cert; const char *cert; const char *key; } ssl; }; struct privdata { int dtor_counter; }; struct pushCounters { int nil; int str; }; static int insecure_calloc_calls; #ifdef HIREDICT_TEST_SSL redictSSLContext *_ssl_ctx = NULL; #endif /* The following lines make up our testing "framework" :) */ static int tests = 0, fails = 0, skips = 0; #define test(_s) { printf("#%02d ", ++tests); printf(_s); } #define test_cond(_c) if(_c) printf("\033[0;32mPASSED\033[0;0m\n"); else {printf("\033[0;31mFAILED\033[0;0m\n"); fails++;} #define test_skipped() { printf("\033[01;33mSKIPPED\033[0;0m\n"); skips++; } static void millisleep(int ms) { #ifdef _MSC_VER Sleep(ms); #else usleep(ms*1000); #endif } static long long usec(void) { #ifndef _MSC_VER struct timeval tv; gettimeofday(&tv,NULL); return (((long long)tv.tv_sec)*1000000)+tv.tv_usec; #else FILETIME ft; GetSystemTimeAsFileTime(&ft); return (((long long)ft.dwHighDateTime << 32) | ft.dwLowDateTime) / 10; #endif } /* The assert() calls below have side effects, so we need assert() * even if we are compiling without asserts (-DNDEBUG). */ #ifdef NDEBUG #undef assert #define assert(e) (void)(e) #endif #define redictTestPanic(msg) \ do { \ fprintf(stderr, "PANIC: %s (In function \"%s\", file \"%s\", line %d)\n", \ msg, __func__, __FILE__, __LINE__); \ exit(1); \ } while (1) /* Helper to extract Redict version information. Aborts on any failure. */ #define REDICT_VERSION_FIELD "redict_version:" void get_redict_version(redictContext *c, int *majorptr, int *minorptr) { redictReply *reply; char *eptr, *s, *e; int major, minor; reply = redictCommand(c, "INFO"); if (reply == NULL || c->err || reply->type != REDICT_REPLY_STRING) goto abort; if ((s = strstr(reply->str, REDICT_VERSION_FIELD)) == NULL) goto abort; s += strlen(REDICT_VERSION_FIELD); /* We need a field terminator and at least 'x.y.z' (5) bytes of data */ if ((e = strstr(s, "\r\n")) == NULL || (e - s) < 5) goto abort; /* Extract version info */ major = strtol(s, &eptr, 10); if (*eptr != '.') goto abort; minor = strtol(eptr+1, NULL, 10); /* Push info the caller wants */ if (majorptr) *majorptr = major; if (minorptr) *minorptr = minor; freeReplyObject(reply); return; abort: freeReplyObject(reply); fprintf(stderr, "Error: Cannot determine Redict version, aborting\n"); exit(1); } static redictContext *select_database(redictContext *c) { redictReply *reply; /* Switch to DB 9 for testing, now that we know we can chat. */ reply = redictCommand(c,"SELECT 9"); assert(reply != NULL); freeReplyObject(reply); /* Make sure the DB is empty */ reply = redictCommand(c,"DBSIZE"); assert(reply != NULL); if (reply->type == REDICT_REPLY_INTEGER && reply->integer == 0) { /* Awesome, DB 9 is empty and we can continue. */ freeReplyObject(reply); } else { printf("Database #9 is not empty, test can not continue\n"); exit(1); } return c; } /* Switch protocol */ static void send_hello(redictContext *c, int version) { redictReply *reply; int expected; reply = redictCommand(c, "HELLO %d", version); expected = version == 3 ? REDICT_REPLY_MAP : REDICT_REPLY_ARRAY; assert(reply != NULL && reply->type == expected); freeReplyObject(reply); } /* Togggle client tracking */ static void send_client_tracking(redictContext *c, const char *str) { redictReply *reply; reply = redictCommand(c, "CLIENT TRACKING %s", str); assert(reply != NULL && reply->type == REDICT_REPLY_STATUS); freeReplyObject(reply); } static int disconnect(redictContext *c, int keep_fd) { redictReply *reply; /* Make sure we're on DB 9. */ reply = redictCommand(c,"SELECT 9"); assert(reply != NULL); freeReplyObject(reply); reply = redictCommand(c,"FLUSHDB"); assert(reply != NULL); freeReplyObject(reply); /* Free the context as well, but keep the fd if requested. */ if (keep_fd) return redictFreeKeepFd(c); redictFree(c); return -1; } static void do_ssl_handshake(redictContext *c) { #ifdef HIREDICT_TEST_SSL redictInitiateSSLWithContext(c, _ssl_ctx); if (c->err) { printf("SSL error: %s\n", c->errstr); redictFree(c); exit(1); } #else (void) c; #endif } static redictContext *do_connect(struct config config) { redictContext *c = NULL; if (config.type == CONN_TCP) { c = redictConnect(config.tcp.host, config.tcp.port); } else if (config.type == CONN_SSL) { c = redictConnect(config.ssl.host, config.ssl.port); } else if (config.type == CONN_UNIX) { c = redictConnectUnix(config.unix_sock.path); } else if (config.type == CONN_FD) { /* Create a dummy connection just to get an fd to inherit */ redictContext *dummy_ctx = redictConnectUnix(config.unix_sock.path); if (dummy_ctx) { int fd = disconnect(dummy_ctx, 1); printf("Connecting to inherited fd %d\n", fd); c = redictConnectFd(fd); } } else { redictTestPanic("Unknown connection type!"); } if (c == NULL) { printf("Connection error: can't allocate redict context\n"); exit(1); } else if (c->err) { printf("Connection error: %s\n", c->errstr); redictFree(c); exit(1); } if (config.type == CONN_SSL) { do_ssl_handshake(c); } return select_database(c); } static void do_reconnect(redictContext *c, struct config config) { redictReconnect(c); if (config.type == CONN_SSL) { do_ssl_handshake(c); } } static void test_format_commands(void) { char *cmd; int len; test("Format command without interpolation: "); len = redictFormatCommand(&cmd,"SET foo bar"); test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 && len == 4+4+(3+2)+4+(3+2)+4+(3+2)); hi_free(cmd); test("Format command with %%s string interpolation: "); len = redictFormatCommand(&cmd,"SET %s %s","foo","bar"); test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 && len == 4+4+(3+2)+4+(3+2)+4+(3+2)); hi_free(cmd); test("Format command with %%s and an empty string: "); len = redictFormatCommand(&cmd,"SET %s %s","foo",""); test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$0\r\n\r\n",len) == 0 && len == 4+4+(3+2)+4+(3+2)+4+(0+2)); hi_free(cmd); test("Format command with an empty string in between proper interpolations: "); len = redictFormatCommand(&cmd,"SET %s %s","","foo"); test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$0\r\n\r\n$3\r\nfoo\r\n",len) == 0 && len == 4+4+(3+2)+4+(0+2)+4+(3+2)); hi_free(cmd); test("Format command with %%b string interpolation: "); len = redictFormatCommand(&cmd,"SET %b %b","foo",(size_t)3,"b\0r",(size_t)3); test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nb\0r\r\n",len) == 0 && len == 4+4+(3+2)+4+(3+2)+4+(3+2)); hi_free(cmd); test("Format command with %%b and an empty string: "); len = redictFormatCommand(&cmd,"SET %b %b","foo",(size_t)3,"",(size_t)0); test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$0\r\n\r\n",len) == 0 && len == 4+4+(3+2)+4+(3+2)+4+(0+2)); hi_free(cmd); test("Format command with literal %%: "); len = redictFormatCommand(&cmd,"SET %% %%"); test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$1\r\n%\r\n$1\r\n%\r\n",len) == 0 && len == 4+4+(3+2)+4+(1+2)+4+(1+2)); hi_free(cmd); /* Vararg width depends on the type. These tests make sure that the * width is correctly determined using the format and subsequent varargs * can correctly be interpolated. */ #define INTEGER_WIDTH_TEST(fmt, type) do { \ type value = 123; \ test("Format command with printf-delegation (" #type "): "); \ len = redictFormatCommand(&cmd,"key:%08" fmt " str:%s", value, "hello"); \ test_cond(strncmp(cmd,"*2\r\n$12\r\nkey:00000123\r\n$9\r\nstr:hello\r\n",len) == 0 && \ len == 4+5+(12+2)+4+(9+2)); \ hi_free(cmd); \ } while(0) #define FLOAT_WIDTH_TEST(type) do { \ type value = 123.0; \ test("Format command with printf-delegation (" #type "): "); \ len = redictFormatCommand(&cmd,"key:%08.3f str:%s", value, "hello"); \ test_cond(strncmp(cmd,"*2\r\n$12\r\nkey:0123.000\r\n$9\r\nstr:hello\r\n",len) == 0 && \ len == 4+5+(12+2)+4+(9+2)); \ hi_free(cmd); \ } while(0) INTEGER_WIDTH_TEST("d", int); INTEGER_WIDTH_TEST("hhd", char); INTEGER_WIDTH_TEST("hd", short); INTEGER_WIDTH_TEST("ld", long); INTEGER_WIDTH_TEST("lld", long long); INTEGER_WIDTH_TEST("u", unsigned int); INTEGER_WIDTH_TEST("hhu", unsigned char); INTEGER_WIDTH_TEST("hu", unsigned short); INTEGER_WIDTH_TEST("lu", unsigned long); INTEGER_WIDTH_TEST("llu", unsigned long long); FLOAT_WIDTH_TEST(float); FLOAT_WIDTH_TEST(double); test("Format command with unhandled printf format (specifier 'p' not supported): "); len = redictFormatCommand(&cmd,"key:%08p %b",(void*)1234,"foo",(size_t)3); test_cond(len == -1); test("Format command with invalid printf format (specifier missing): "); len = redictFormatCommand(&cmd,"%-"); test_cond(len == -1); const char *argv[3]; argv[0] = "SET"; argv[1] = "foo\0xxx"; argv[2] = "bar"; size_t lens[3] = { 3, 7, 3 }; int argc = 3; test("Format command by passing argc/argv without lengths: "); len = redictFormatCommandArgv(&cmd,argc,argv,NULL); test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 && len == 4+4+(3+2)+4+(3+2)+4+(3+2)); hi_free(cmd); test("Format command by passing argc/argv with lengths: "); len = redictFormatCommandArgv(&cmd,argc,argv,lens); test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$7\r\nfoo\0xxx\r\n$3\r\nbar\r\n",len) == 0 && len == 4+4+(3+2)+4+(7+2)+4+(3+2)); hi_free(cmd); sds sds_cmd; sds_cmd = NULL; test("Format command into sds by passing argc/argv without lengths: "); len = redictFormatSdsCommandArgv(&sds_cmd,argc,argv,NULL); test_cond(strncmp(sds_cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 && len == 4+4+(3+2)+4+(3+2)+4+(3+2)); sdsfree(sds_cmd); sds_cmd = NULL; test("Format command into sds by passing argc/argv with lengths: "); len = redictFormatSdsCommandArgv(&sds_cmd,argc,argv,lens); test_cond(strncmp(sds_cmd,"*3\r\n$3\r\nSET\r\n$7\r\nfoo\0xxx\r\n$3\r\nbar\r\n",len) == 0 && len == 4+4+(3+2)+4+(7+2)+4+(3+2)); sdsfree(sds_cmd); } static void test_append_formatted_commands(struct config config) { redictContext *c; redictReply *reply; char *cmd; int len; c = do_connect(config); test("Append format command: "); len = redictFormatCommand(&cmd, "SET foo bar"); test_cond(redictAppendFormattedCommand(c, cmd, len) == REDICT_OK); assert(redictGetReply(c, (void*)&reply) == REDICT_OK); hi_free(cmd); freeReplyObject(reply); disconnect(c, 0); } static void test_tcp_options(struct config cfg) { redictContext *c; c = do_connect(cfg); test("We can enable TCP_KEEPALIVE: "); test_cond(redictEnableKeepAlive(c) == REDICT_OK); #ifdef TCP_USER_TIMEOUT test("We can set TCP_USER_TIMEOUT: "); test_cond(redictSetTcpUserTimeout(c, 100) == REDICT_OK); #else test("Setting TCP_USER_TIMEOUT errors when unsupported: "); test_cond(redictSetTcpUserTimeout(c, 100) == REDICT_ERR && c->err == REDICT_ERR_IO); #endif redictFree(c); } static void test_unix_keepalive(struct config cfg) { redictContext *c; redictReply *r; c = do_connect(cfg); test("Setting TCP_KEEPALIVE on a unix socket returns an error: "); test_cond(redictEnableKeepAlive(c) == REDICT_ERR && c->err == 0); test("Setting TCP_KEEPALIVE on a unix socket doesn't break the connection: "); r = redictCommand(c, "PING"); test_cond(r != NULL && r->type == REDICT_REPLY_STATUS && r->len == 4 && !memcmp(r->str, "PONG", 4)); freeReplyObject(r); redictFree(c); } static void test_reply_reader(void) { redictReader *reader; void *reply, *root; int ret; int i; test("Error handling in reply parser: "); reader = redictReaderCreate(); redictReaderFeed(reader,(char*)"@foo\r\n",6); ret = redictReaderGetReply(reader,NULL); test_cond(ret == REDICT_ERR && strcasecmp(reader->errstr,"Protocol error, got \"@\" as reply type byte") == 0); redictReaderFree(reader); /* when the reply already contains multiple items, they must be free'd * on an error. valgrind will bark when this doesn't happen. */ test("Memory cleanup in reply parser: "); reader = redictReaderCreate(); redictReaderFeed(reader,(char*)"*2\r\n",4); redictReaderFeed(reader,(char*)"$5\r\nhello\r\n",11); redictReaderFeed(reader,(char*)"@foo\r\n",6); ret = redictReaderGetReply(reader,NULL); test_cond(ret == REDICT_ERR && strcasecmp(reader->errstr,"Protocol error, got \"@\" as reply type byte") == 0); redictReaderFree(reader); reader = redictReaderCreate(); test("Can handle arbitrarily nested multi-bulks: "); for (i = 0; i < 128; i++) { redictReaderFeed(reader,(char*)"*1\r\n", 4); } redictReaderFeed(reader,(char*)"$6\r\nLOLWUT\r\n",12); ret = redictReaderGetReply(reader,&reply); root = reply; /* Keep track of the root reply */ test_cond(ret == REDICT_OK && ((redictReply*)reply)->type == REDICT_REPLY_ARRAY && ((redictReply*)reply)->elements == 1); test("Can parse arbitrarily nested multi-bulks correctly: "); while(i--) { assert(reply != NULL && ((redictReply*)reply)->type == REDICT_REPLY_ARRAY); reply = ((redictReply*)reply)->element[0]; } test_cond(((redictReply*)reply)->type == REDICT_REPLY_STRING && !memcmp(((redictReply*)reply)->str, "LOLWUT", 6)); freeReplyObject(root); redictReaderFree(reader); test("Correctly parses LLONG_MAX: "); reader = redictReaderCreate(); redictReaderFeed(reader, ":9223372036854775807\r\n",22); ret = redictReaderGetReply(reader,&reply); test_cond(ret == REDICT_OK && ((redictReply*)reply)->type == REDICT_REPLY_INTEGER && ((redictReply*)reply)->integer == LLONG_MAX); freeReplyObject(reply); redictReaderFree(reader); test("Set error when > LLONG_MAX: "); reader = redictReaderCreate(); redictReaderFeed(reader, ":9223372036854775808\r\n",22); ret = redictReaderGetReply(reader,&reply); test_cond(ret == REDICT_ERR && strcasecmp(reader->errstr,"Bad integer value") == 0); freeReplyObject(reply); redictReaderFree(reader); test("Correctly parses LLONG_MIN: "); reader = redictReaderCreate(); redictReaderFeed(reader, ":-9223372036854775808\r\n",23); ret = redictReaderGetReply(reader,&reply); test_cond(ret == REDICT_OK && ((redictReply*)reply)->type == REDICT_REPLY_INTEGER && ((redictReply*)reply)->integer == LLONG_MIN); freeReplyObject(reply); redictReaderFree(reader); test("Set error when < LLONG_MIN: "); reader = redictReaderCreate(); redictReaderFeed(reader, ":-9223372036854775809\r\n",23); ret = redictReaderGetReply(reader,&reply); test_cond(ret == REDICT_ERR && strcasecmp(reader->errstr,"Bad integer value") == 0); freeReplyObject(reply); redictReaderFree(reader); test("Set error when array < -1: "); reader = redictReaderCreate(); redictReaderFeed(reader, "*-2\r\n+asdf\r\n",12); ret = redictReaderGetReply(reader,&reply); test_cond(ret == REDICT_ERR && strcasecmp(reader->errstr,"Multi-bulk length out of range") == 0); freeReplyObject(reply); redictReaderFree(reader); test("Set error when bulk < -1: "); reader = redictReaderCreate(); redictReaderFeed(reader, "$-2\r\nasdf\r\n",11); ret = redictReaderGetReply(reader,&reply); test_cond(ret == REDICT_ERR && strcasecmp(reader->errstr,"Bulk string length out of range") == 0); freeReplyObject(reply); redictReaderFree(reader); test("Can configure maximum multi-bulk elements: "); reader = redictReaderCreate(); reader->maxelements = 1024; redictReaderFeed(reader, "*1025\r\n", 7); ret = redictReaderGetReply(reader,&reply); test_cond(ret == REDICT_ERR && strcasecmp(reader->errstr, "Multi-bulk length out of range") == 0); freeReplyObject(reply); redictReaderFree(reader); test("Multi-bulk never overflows regardless of maxelements: "); size_t bad_mbulk_len = (SIZE_MAX / sizeof(void *)) + 3; char bad_mbulk_reply[100]; snprintf(bad_mbulk_reply, sizeof(bad_mbulk_reply), "*%llu\r\n+asdf\r\n", (unsigned long long) bad_mbulk_len); reader = redictReaderCreate(); reader->maxelements = 0; /* Don't rely on default limit */ redictReaderFeed(reader, bad_mbulk_reply, strlen(bad_mbulk_reply)); ret = redictReaderGetReply(reader,&reply); test_cond(ret == REDICT_ERR && strcasecmp(reader->errstr, "Out of memory") == 0); freeReplyObject(reply); redictReaderFree(reader); #if LLONG_MAX > SIZE_MAX test("Set error when array > SIZE_MAX: "); reader = redictReaderCreate(); redictReaderFeed(reader, "*9223372036854775807\r\n+asdf\r\n",29); ret = redictReaderGetReply(reader,&reply); test_cond(ret == REDICT_ERR && strcasecmp(reader->errstr,"Multi-bulk length out of range") == 0); freeReplyObject(reply); redictReaderFree(reader); test("Set error when bulk > SIZE_MAX: "); reader = redictReaderCreate(); redictReaderFeed(reader, "$9223372036854775807\r\nasdf\r\n",28); ret = redictReaderGetReply(reader,&reply); test_cond(ret == REDICT_ERR && strcasecmp(reader->errstr,"Bulk string length out of range") == 0); freeReplyObject(reply); redictReaderFree(reader); #endif test("Works with NULL functions for reply: "); reader = redictReaderCreate(); reader->fn = NULL; redictReaderFeed(reader,(char*)"+OK\r\n",5); ret = redictReaderGetReply(reader,&reply); test_cond(ret == REDICT_OK && reply == (void*)REDICT_REPLY_STATUS); redictReaderFree(reader); test("Works when a single newline (\\r\\n) covers two calls to feed: "); reader = redictReaderCreate(); reader->fn = NULL; redictReaderFeed(reader,(char*)"+OK\r",4); ret = redictReaderGetReply(reader,&reply); assert(ret == REDICT_OK && reply == NULL); redictReaderFeed(reader,(char*)"\n",1); ret = redictReaderGetReply(reader,&reply); test_cond(ret == REDICT_OK && reply == (void*)REDICT_REPLY_STATUS); redictReaderFree(reader); test("Don't reset state after protocol error: "); reader = redictReaderCreate(); reader->fn = NULL; redictReaderFeed(reader,(char*)"x",1); ret = redictReaderGetReply(reader,&reply); assert(ret == REDICT_ERR); ret = redictReaderGetReply(reader,&reply); test_cond(ret == REDICT_ERR && reply == NULL); redictReaderFree(reader); test("Don't reset state after protocol error(not segfault): "); reader = redictReaderCreate(); redictReaderFeed(reader,(char*)"*3\r\n$3\r\nSET\r\n$5\r\nhello\r\n$", 25); ret = redictReaderGetReply(reader,&reply); assert(ret == REDICT_OK); redictReaderFeed(reader,(char*)"3\r\nval\r\n", 8); ret = redictReaderGetReply(reader,&reply); test_cond(ret == REDICT_OK && ((redictReply*)reply)->type == REDICT_REPLY_ARRAY && ((redictReply*)reply)->elements == 3); freeReplyObject(reply); redictReaderFree(reader); /* Regression test for issue #45 on GitHub. */ test("Don't do empty allocation for empty multi bulk: "); reader = redictReaderCreate(); redictReaderFeed(reader,(char*)"*0\r\n",4); ret = redictReaderGetReply(reader,&reply); test_cond(ret == REDICT_OK && ((redictReply*)reply)->type == REDICT_REPLY_ARRAY && ((redictReply*)reply)->elements == 0); freeReplyObject(reply); redictReaderFree(reader); /* RESP3 verbatim strings (GitHub issue #802) */ test("Can parse RESP3 verbatim strings: "); reader = redictReaderCreate(); redictReaderFeed(reader,(char*)"=10\r\ntxt:LOLWUT\r\n",17); ret = redictReaderGetReply(reader,&reply); test_cond(ret == REDICT_OK && ((redictReply*)reply)->type == REDICT_REPLY_VERB && !memcmp(((redictReply*)reply)->str,"LOLWUT", 6)); freeReplyObject(reply); redictReaderFree(reader); /* RESP3 push messages (Github issue #815) */ test("Can parse RESP3 push messages: "); reader = redictReaderCreate(); redictReaderFeed(reader,(char*)">2\r\n$6\r\nLOLWUT\r\n:42\r\n",21); ret = redictReaderGetReply(reader,&reply); test_cond(ret == REDICT_OK && ((redictReply*)reply)->type == REDICT_REPLY_PUSH && ((redictReply*)reply)->elements == 2 && ((redictReply*)reply)->element[0]->type == REDICT_REPLY_STRING && !memcmp(((redictReply*)reply)->element[0]->str,"LOLWUT",6) && ((redictReply*)reply)->element[1]->type == REDICT_REPLY_INTEGER && ((redictReply*)reply)->element[1]->integer == 42); freeReplyObject(reply); redictReaderFree(reader); test("Can parse RESP3 doubles: "); reader = redictReaderCreate(); redictReaderFeed(reader, ",3.14159265358979323846\r\n",25); ret = redictReaderGetReply(reader,&reply); test_cond(ret == REDICT_OK && ((redictReply*)reply)->type == REDICT_REPLY_DOUBLE && fabs(((redictReply*)reply)->dval - 3.14159265358979323846) < 0.00000001 && ((redictReply*)reply)->len == 22 && strcmp(((redictReply*)reply)->str, "3.14159265358979323846") == 0); freeReplyObject(reply); redictReaderFree(reader); test("Set error on invalid RESP3 double: "); reader = redictReaderCreate(); redictReaderFeed(reader, ",3.14159\000265358979323846\r\n",26); ret = redictReaderGetReply(reader,&reply); test_cond(ret == REDICT_ERR && strcasecmp(reader->errstr,"Bad double value") == 0); freeReplyObject(reply); redictReaderFree(reader); test("Correctly parses RESP3 double INFINITY: "); reader = redictReaderCreate(); redictReaderFeed(reader, ",inf\r\n",6); ret = redictReaderGetReply(reader,&reply); test_cond(ret == REDICT_OK && ((redictReply*)reply)->type == REDICT_REPLY_DOUBLE && isinf(((redictReply*)reply)->dval) && ((redictReply*)reply)->dval > 0); freeReplyObject(reply); redictReaderFree(reader); test("Correctly parses RESP3 double NaN: "); reader = redictReaderCreate(); redictReaderFeed(reader, ",nan\r\n",6); ret = redictReaderGetReply(reader,&reply); test_cond(ret == REDICT_OK && ((redictReply*)reply)->type == REDICT_REPLY_DOUBLE && isnan(((redictReply*)reply)->dval)); freeReplyObject(reply); redictReaderFree(reader); test("Correctly parses RESP3 double -Nan: "); reader = redictReaderCreate(); redictReaderFeed(reader, ",-nan\r\n", 7); ret = redictReaderGetReply(reader, &reply); test_cond(ret == REDICT_OK && ((redictReply*)reply)->type == REDICT_REPLY_DOUBLE && isnan(((redictReply*)reply)->dval)); freeReplyObject(reply); redictReaderFree(reader); test("Can parse RESP3 nil: "); reader = redictReaderCreate(); redictReaderFeed(reader, "_\r\n",3); ret = redictReaderGetReply(reader,&reply); test_cond(ret == REDICT_OK && ((redictReply*)reply)->type == REDICT_REPLY_NIL); freeReplyObject(reply); redictReaderFree(reader); test("Set error on invalid RESP3 nil: "); reader = redictReaderCreate(); redictReaderFeed(reader, "_nil\r\n",6); ret = redictReaderGetReply(reader,&reply); test_cond(ret == REDICT_ERR && strcasecmp(reader->errstr,"Bad nil value") == 0); freeReplyObject(reply); redictReaderFree(reader); test("Can parse RESP3 bool (true): "); reader = redictReaderCreate(); redictReaderFeed(reader, "#t\r\n",4); ret = redictReaderGetReply(reader,&reply); test_cond(ret == REDICT_OK && ((redictReply*)reply)->type == REDICT_REPLY_BOOL && ((redictReply*)reply)->integer); freeReplyObject(reply); redictReaderFree(reader); test("Can parse RESP3 bool (false): "); reader = redictReaderCreate(); redictReaderFeed(reader, "#f\r\n",4); ret = redictReaderGetReply(reader,&reply); test_cond(ret == REDICT_OK && ((redictReply*)reply)->type == REDICT_REPLY_BOOL && !((redictReply*)reply)->integer); freeReplyObject(reply); redictReaderFree(reader); test("Set error on invalid RESP3 bool: "); reader = redictReaderCreate(); redictReaderFeed(reader, "#foobar\r\n",9); ret = redictReaderGetReply(reader,&reply); test_cond(ret == REDICT_ERR && strcasecmp(reader->errstr,"Bad bool value") == 0); freeReplyObject(reply); redictReaderFree(reader); test("Can parse RESP3 map: "); reader = redictReaderCreate(); redictReaderFeed(reader, "%2\r\n+first\r\n:123\r\n$6\r\nsecond\r\n#t\r\n",34); ret = redictReaderGetReply(reader,&reply); test_cond(ret == REDICT_OK && ((redictReply*)reply)->type == REDICT_REPLY_MAP && ((redictReply*)reply)->elements == 4 && ((redictReply*)reply)->element[0]->type == REDICT_REPLY_STATUS && ((redictReply*)reply)->element[0]->len == 5 && !strcmp(((redictReply*)reply)->element[0]->str,"first") && ((redictReply*)reply)->element[1]->type == REDICT_REPLY_INTEGER && ((redictReply*)reply)->element[1]->integer == 123 && ((redictReply*)reply)->element[2]->type == REDICT_REPLY_STRING && ((redictReply*)reply)->element[2]->len == 6 && !strcmp(((redictReply*)reply)->element[2]->str,"second") && ((redictReply*)reply)->element[3]->type == REDICT_REPLY_BOOL && ((redictReply*)reply)->element[3]->integer); freeReplyObject(reply); redictReaderFree(reader); test("Can parse RESP3 attribute: "); reader = redictReaderCreate(); redictReaderFeed(reader, "|2\r\n+foo\r\n:123\r\n+bar\r\n#t\r\n",26); ret = redictReaderGetReply(reader,&reply); test_cond(ret == REDICT_OK && ((redictReply*)reply)->type == REDICT_REPLY_ATTR && ((redictReply*)reply)->elements == 4 && ((redictReply*)reply)->element[0]->type == REDICT_REPLY_STATUS && ((redictReply*)reply)->element[0]->len == 3 && !strcmp(((redictReply*)reply)->element[0]->str,"foo") && ((redictReply*)reply)->element[1]->type == REDICT_REPLY_INTEGER && ((redictReply*)reply)->element[1]->integer == 123 && ((redictReply*)reply)->element[2]->type == REDICT_REPLY_STATUS && ((redictReply*)reply)->element[2]->len == 3 && !strcmp(((redictReply*)reply)->element[2]->str,"bar") && ((redictReply*)reply)->element[3]->type == REDICT_REPLY_BOOL && ((redictReply*)reply)->element[3]->integer); freeReplyObject(reply); redictReaderFree(reader); test("Can parse RESP3 set: "); reader = redictReaderCreate(); redictReaderFeed(reader, "~5\r\n+orange\r\n$5\r\napple\r\n#f\r\n:100\r\n:999\r\n",40); ret = redictReaderGetReply(reader,&reply); test_cond(ret == REDICT_OK && ((redictReply*)reply)->type == REDICT_REPLY_SET && ((redictReply*)reply)->elements == 5 && ((redictReply*)reply)->element[0]->type == REDICT_REPLY_STATUS && ((redictReply*)reply)->element[0]->len == 6 && !strcmp(((redictReply*)reply)->element[0]->str,"orange") && ((redictReply*)reply)->element[1]->type == REDICT_REPLY_STRING && ((redictReply*)reply)->element[1]->len == 5 && !strcmp(((redictReply*)reply)->element[1]->str,"apple") && ((redictReply*)reply)->element[2]->type == REDICT_REPLY_BOOL && !((redictReply*)reply)->element[2]->integer && ((redictReply*)reply)->element[3]->type == REDICT_REPLY_INTEGER && ((redictReply*)reply)->element[3]->integer == 100 && ((redictReply*)reply)->element[4]->type == REDICT_REPLY_INTEGER && ((redictReply*)reply)->element[4]->integer == 999); freeReplyObject(reply); redictReaderFree(reader); test("Can parse RESP3 bignum: "); reader = redictReaderCreate(); redictReaderFeed(reader,"(3492890328409238509324850943850943825024385\r\n",46); ret = redictReaderGetReply(reader,&reply); test_cond(ret == REDICT_OK && ((redictReply*)reply)->type == REDICT_REPLY_BIGNUM && ((redictReply*)reply)->len == 43 && !strcmp(((redictReply*)reply)->str,"3492890328409238509324850943850943825024385")); freeReplyObject(reply); redictReaderFree(reader); test("Can parse RESP3 doubles in an array: "); reader = redictReaderCreate(); redictReaderFeed(reader, "*1\r\n,3.14159265358979323846\r\n",31); ret = redictReaderGetReply(reader,&reply); test_cond(ret == REDICT_OK && ((redictReply*)reply)->type == REDICT_REPLY_ARRAY && ((redictReply*)reply)->elements == 1 && ((redictReply*)reply)->element[0]->type == REDICT_REPLY_DOUBLE && fabs(((redictReply*)reply)->element[0]->dval - 3.14159265358979323846) < 0.00000001 && ((redictReply*)reply)->element[0]->len == 22 && strcmp(((redictReply*)reply)->element[0]->str, "3.14159265358979323846") == 0); freeReplyObject(reply); redictReaderFree(reader); } static void test_free_null(void) { void *redictCtx = NULL; void *reply = NULL; test("Don't fail when redictFree is passed a NULL value: "); redictFree(redictCtx); test_cond(redictCtx == NULL); test("Don't fail when freeReplyObject is passed a NULL value: "); freeReplyObject(reply); test_cond(reply == NULL); } static void *hi_malloc_fail(size_t size) { (void)size; return NULL; } static void *hi_calloc_fail(size_t nmemb, size_t size) { (void)nmemb; (void)size; return NULL; } static void *hi_calloc_insecure(size_t nmemb, size_t size) { (void)nmemb; (void)size; insecure_calloc_calls++; return (void*)0xdeadc0de; } static void *hi_realloc_fail(void *ptr, size_t size) { (void)ptr; (void)size; return NULL; } static void test_allocator_injection(void) { void *ptr; hiredictAllocFuncs ha = { .mallocFn = hi_malloc_fail, .callocFn = hi_calloc_fail, .reallocFn = hi_realloc_fail, .strdupFn = strdup, .freeFn = free, }; // Override hiredict allocators hiredictSetAllocators(&ha); test("redictContext uses injected allocators: "); redictContext *c = redictConnect("localhost", 6379); test_cond(c == NULL); test("redictReader uses injected allocators: "); redictReader *reader = redictReaderCreate(); test_cond(reader == NULL); /* Make sure hiredict itself protects against a non-overflow checking calloc */ test("hiredict calloc wrapper protects against overflow: "); ha.callocFn = hi_calloc_insecure; hiredictSetAllocators(&ha); ptr = hi_calloc((SIZE_MAX / sizeof(void*)) + 3, sizeof(void*)); test_cond(ptr == NULL && insecure_calloc_calls == 0); // Return allocators to default hiredictResetAllocators(); } #define HIREDICT_BAD_DOMAIN "idontexist-noreally.com" static void test_blocking_connection_errors(void) { struct addrinfo hints = {.ai_family = AF_INET}; struct addrinfo *ai_tmp = NULL; redictContext *c; int rv = getaddrinfo(HIREDICT_BAD_DOMAIN, "6379", &hints, &ai_tmp); if (rv != 0) { // Address does *not* exist test("Returns error when host cannot be resolved: "); // First see if this domain name *actually* resolves to NXDOMAIN c = redictConnect(HIREDICT_BAD_DOMAIN, 6379); test_cond( c->err == REDICT_ERR_OTHER && (strcmp(c->errstr, "Name or service not known") == 0 || strcmp(c->errstr, "Can't resolve: " HIREDICT_BAD_DOMAIN) == 0 || strcmp(c->errstr, "Name does not resolve") == 0 || strcmp(c->errstr, "nodename nor servname provided, or not known") == 0 || strcmp(c->errstr, "node name or service name not known") == 0 || strcmp(c->errstr, "No address associated with hostname") == 0 || strcmp(c->errstr, "Temporary failure in name resolution") == 0 || strcmp(c->errstr, "hostname nor servname provided, or not known") == 0 || strcmp(c->errstr, "no address associated with name") == 0 || strcmp(c->errstr, "No such host is known. ") == 0)); redictFree(c); } else { printf("Skipping NXDOMAIN test. Found evil ISP!\n"); freeaddrinfo(ai_tmp); } #ifndef _WIN32 redictOptions opt = {0}; struct timeval tv; test("Returns error when the port is not open: "); c = redictConnect((char*)"localhost", 1); test_cond(c->err == REDICT_ERR_IO && strcmp(c->errstr,"Connection refused") == 0); redictFree(c); /* Verify we don't regress from the fix in PR #1180 */ test("We don't clobber connection exception with setsockopt error: "); tv = (struct timeval){.tv_sec = 0, .tv_usec = 500000}; opt.command_timeout = opt.connect_timeout = &tv; REDICT_OPTIONS_SET_TCP(&opt, "localhost", 10337); c = redictConnectWithOptions(&opt); test_cond(c->err == REDICT_ERR_IO && strcmp(c->errstr, "Connection refused") == 0); redictFree(c); test("Returns error when the unix_sock socket path doesn't accept connections: "); c = redictConnectUnix((char*)"/tmp/idontexist.sock"); test_cond(c->err == REDICT_ERR_IO); /* Don't care about the message... */ redictFree(c); #endif } /* Test push handler */ void push_handler(void *privdata, void *r) { struct pushCounters *pcounts = privdata; redictReply *reply = r, *payload; assert(reply && reply->type == REDICT_REPLY_PUSH && reply->elements == 2); payload = reply->element[1]; if (payload->type == REDICT_REPLY_ARRAY) { payload = payload->element[0]; } if (payload->type == REDICT_REPLY_STRING) { pcounts->str++; } else if (payload->type == REDICT_REPLY_NIL) { pcounts->nil++; } freeReplyObject(reply); } /* Dummy function just to test setting a callback with redictOptions */ void push_handler_async(redictAsyncContext *ac, void *reply) { (void)ac; (void)reply; } static void test_resp3_push_handler(redictContext *c) { struct pushCounters pc = {0}; redictPushFn *old = NULL; redictReply *reply; void *privdata; /* Switch to RESP3 and turn on client tracking */ send_hello(c, 3); send_client_tracking(c, "ON"); privdata = c->privdata; c->privdata = &pc; reply = redictCommand(c, "GET key:0"); assert(reply != NULL); freeReplyObject(reply); test("RESP3 PUSH messages are handled out of band by default: "); reply = redictCommand(c, "SET key:0 val:0"); test_cond(reply != NULL && reply->type == REDICT_REPLY_STATUS); freeReplyObject(reply); assert((reply = redictCommand(c, "GET key:0")) != NULL); freeReplyObject(reply); old = redictSetPushCallback(c, push_handler); test("We can set a custom RESP3 PUSH handler: "); reply = redictCommand(c, "SET key:0 val:0"); /* We need another command because depending on the version of Redict, the * notification may be delivered after the command's reply. */ assert(reply != NULL); freeReplyObject(reply); reply = redictCommand(c, "PING"); test_cond(reply != NULL && reply->type == REDICT_REPLY_STATUS && pc.str == 1); freeReplyObject(reply); test("We properly handle a NIL invalidation payload: "); reply = redictCommand(c, "FLUSHDB"); assert(reply != NULL); freeReplyObject(reply); reply = redictCommand(c, "PING"); test_cond(reply != NULL && reply->type == REDICT_REPLY_STATUS && pc.nil == 1); freeReplyObject(reply); /* Unset the push callback and generate an invalidate message making * sure it is not handled out of band. */ test("With no handler, PUSH replies come in-band: "); redictSetPushCallback(c, NULL); assert((reply = redictCommand(c, "GET key:0")) != NULL); freeReplyObject(reply); assert((reply = redictCommand(c, "SET key:0 invalid")) != NULL); /* Depending on Redict version, we may receive either push notification or * status reply. Both cases are valid. */ if (reply->type == REDICT_REPLY_STATUS) { freeReplyObject(reply); reply = redictCommand(c, "PING"); } test_cond(reply->type == REDICT_REPLY_PUSH); freeReplyObject(reply); test("With no PUSH handler, no replies are lost: "); assert(redictGetReply(c, (void**)&reply) == REDICT_OK); test_cond(reply != NULL && reply->type == REDICT_REPLY_STATUS); freeReplyObject(reply); /* Return to the originally set PUSH handler */ assert(old != NULL); redictSetPushCallback(c, old); /* Switch back to RESP2 and disable tracking */ c->privdata = privdata; send_client_tracking(c, "OFF"); send_hello(c, 2); } redictOptions get_redict_tcp_options(struct config config) { redictOptions options = {0}; REDICT_OPTIONS_SET_TCP(&options, config.tcp.host, config.tcp.port); return options; } static void test_resp3_push_options(struct config config) { redictAsyncContext *ac; redictContext *c; redictOptions options; test("We set a default RESP3 handler for redictContext: "); options = get_redict_tcp_options(config); assert((c = redictConnectWithOptions(&options)) != NULL); test_cond(c->push_cb != NULL); redictFree(c); test("We don't set a default RESP3 push handler for redictAsyncContext: "); options = get_redict_tcp_options(config); assert((ac = redictAsyncConnectWithOptions(&options)) != NULL); test_cond(ac->c.push_cb == NULL); redictAsyncFree(ac); test("Our REDICT_OPT_NO_PUSH_AUTOFREE flag works: "); options = get_redict_tcp_options(config); options.options |= REDICT_OPT_NO_PUSH_AUTOFREE; assert((c = redictConnectWithOptions(&options)) != NULL); test_cond(c->push_cb == NULL); redictFree(c); test("We can use redictOptions to set a custom PUSH handler for redctsContext: "); options = get_redict_tcp_options(config); options.push_cb = push_handler; assert((c = redictConnectWithOptions(&options)) != NULL); test_cond(c->push_cb == push_handler); redictFree(c); test("We can use redictOptions to set a custom PUSH handler for redictAsyncContext: "); options = get_redict_tcp_options(config); options.async_push_cb = push_handler_async; assert((ac = redictAsyncConnectWithOptions(&options)) != NULL); test_cond(ac->push_cb == push_handler_async); redictAsyncFree(ac); } void free_privdata(void *privdata) { struct privdata *data = privdata; data->dtor_counter++; } static void test_privdata_hooks(struct config config) { struct privdata data = {0}; redictOptions options; redictContext *c; test("We can use redictOptions to set privdata: "); options = get_redict_tcp_options(config); REDICT_OPTIONS_SET_PRIVDATA(&options, &data, free_privdata); assert((c = redictConnectWithOptions(&options)) != NULL); test_cond(c->privdata == &data); test("Our privdata destructor fires when we free the context: "); redictFree(c); test_cond(data.dtor_counter == 1); } static void test_blocking_connection(struct config config) { redictContext *c; redictReply *reply; int major; c = do_connect(config); test("Is able to deliver commands: "); reply = redictCommand(c,"PING"); test_cond(reply->type == REDICT_REPLY_STATUS && strcasecmp(reply->str,"pong") == 0) freeReplyObject(reply); test("Is a able to send commands verbatim: "); reply = redictCommand(c,"SET foo bar"); test_cond (reply->type == REDICT_REPLY_STATUS && strcasecmp(reply->str,"ok") == 0) freeReplyObject(reply); test("%%s String interpolation works: "); reply = redictCommand(c,"SET %s %s","foo","hello world"); freeReplyObject(reply); reply = redictCommand(c,"GET foo"); test_cond(reply->type == REDICT_REPLY_STRING && strcmp(reply->str,"hello world") == 0); freeReplyObject(reply); test("%%b String interpolation works: "); reply = redictCommand(c,"SET %b %b","foo",(size_t)3,"hello\x00world",(size_t)11); freeReplyObject(reply); reply = redictCommand(c,"GET foo"); test_cond(reply->type == REDICT_REPLY_STRING && memcmp(reply->str,"hello\x00world",11) == 0) test("Binary reply length is correct: "); test_cond(reply->len == 11) freeReplyObject(reply); test("Can parse nil replies: "); reply = redictCommand(c,"GET nokey"); test_cond(reply->type == REDICT_REPLY_NIL) freeReplyObject(reply); /* test 7 */ test("Can parse integer replies: "); reply = redictCommand(c,"INCR mycounter"); test_cond(reply->type == REDICT_REPLY_INTEGER && reply->integer == 1) freeReplyObject(reply); test("Can parse multi bulk replies: "); freeReplyObject(redictCommand(c,"LPUSH mylist foo")); freeReplyObject(redictCommand(c,"LPUSH mylist bar")); reply = redictCommand(c,"LRANGE mylist 0 -1"); test_cond(reply->type == REDICT_REPLY_ARRAY && reply->elements == 2 && !memcmp(reply->element[0]->str,"bar",3) && !memcmp(reply->element[1]->str,"foo",3)) freeReplyObject(reply); /* m/e with multi bulk reply *before* other reply. * specifically test ordering of reply items to parse. */ test("Can handle nested multi bulk replies: "); freeReplyObject(redictCommand(c,"MULTI")); freeReplyObject(redictCommand(c,"LRANGE mylist 0 -1")); freeReplyObject(redictCommand(c,"PING")); reply = (redictCommand(c,"EXEC")); test_cond(reply->type == REDICT_REPLY_ARRAY && reply->elements == 2 && reply->element[0]->type == REDICT_REPLY_ARRAY && reply->element[0]->elements == 2 && !memcmp(reply->element[0]->element[0]->str,"bar",3) && !memcmp(reply->element[0]->element[1]->str,"foo",3) && reply->element[1]->type == REDICT_REPLY_STATUS && strcasecmp(reply->element[1]->str,"pong") == 0); freeReplyObject(reply); test("Send command by passing argc/argv: "); const char *argv[3] = {"SET", "foo", "bar"}; size_t argvlen[3] = {3, 3, 3}; reply = redictCommandArgv(c,3,argv,argvlen); test_cond(reply->type == REDICT_REPLY_STATUS); freeReplyObject(reply); /* Make sure passing NULL to redictGetReply is safe */ test("Can pass NULL to redictGetReply: "); assert(redictAppendCommand(c, "PING") == REDICT_OK); test_cond(redictGetReply(c, NULL) == REDICT_OK); get_redict_version(c, &major, NULL); if (major >= 6) test_resp3_push_handler(c); test_resp3_push_options(config); test_privdata_hooks(config); disconnect(c, 0); } /* Send DEBUG SLEEP 0 to detect if we have this command */ static int detect_debug_sleep(redictContext *c) { int detected; redictReply *reply = redictCommand(c, "DEBUG SLEEP 0\r\n"); if (reply == NULL || c->err) { const char *cause = c->err ? c->errstr : "(none)"; fprintf(stderr, "Error testing for DEBUG SLEEP (Redict error: %s), exiting\n", cause); exit(-1); } detected = reply->type == REDICT_REPLY_STATUS; freeReplyObject(reply); return detected; } static void test_blocking_connection_timeouts(struct config config) { redictContext *c; redictReply *reply; ssize_t s; const char *sleep_cmd = "DEBUG SLEEP 3\r\n"; struct timeval tv; c = do_connect(config); test("Successfully completes a command when the timeout is not exceeded: "); reply = redictCommand(c,"SET foo fast"); freeReplyObject(reply); tv.tv_sec = 0; tv.tv_usec = 10000; redictSetTimeout(c, tv); reply = redictCommand(c, "GET foo"); test_cond(reply != NULL && reply->type == REDICT_REPLY_STRING && memcmp(reply->str, "fast", 4) == 0); freeReplyObject(reply); disconnect(c, 0); c = do_connect(config); test("Does not return a reply when the command times out: "); if (detect_debug_sleep(c)) { redictAppendFormattedCommand(c, sleep_cmd, strlen(sleep_cmd)); // flush connection buffer without waiting for the reply s = c->funcs->write(c); assert(s == (ssize_t)sdslen(c->obuf)); sdsfree(c->obuf); c->obuf = sdsempty(); tv.tv_sec = 0; tv.tv_usec = 10000; redictSetTimeout(c, tv); reply = redictCommand(c, "GET foo"); #ifndef _WIN32 test_cond(s > 0 && reply == NULL && c->err == REDICT_ERR_IO && strcmp(c->errstr, "Resource temporarily unavailable") == 0); #else test_cond(s > 0 && reply == NULL && c->err == REDICT_ERR_TIMEOUT && strcmp(c->errstr, "recv timeout") == 0); #endif freeReplyObject(reply); // wait for the DEBUG SLEEP to complete so that Redict server is unblocked for the following tests millisleep(3000); } else { test_skipped(); } test("Reconnect properly reconnects after a timeout: "); do_reconnect(c, config); reply = redictCommand(c, "PING"); test_cond(reply != NULL && reply->type == REDICT_REPLY_STATUS && strcmp(reply->str, "PONG") == 0); freeReplyObject(reply); test("Reconnect properly uses owned parameters: "); config.tcp.host = "foo"; config.unix_sock.path = "foo"; do_reconnect(c, config); reply = redictCommand(c, "PING"); test_cond(reply != NULL && reply->type == REDICT_REPLY_STATUS && strcmp(reply->str, "PONG") == 0); freeReplyObject(reply); disconnect(c, 0); } static void test_blocking_io_errors(struct config config) { redictContext *c; redictReply *reply; void *_reply; int major, minor; /* Connect to target given by config. */ c = do_connect(config); get_redict_version(c, &major, &minor); test("Returns I/O error when the connection is lost: "); reply = redictCommand(c,"QUIT"); if (major > 2 || (major == 2 && minor > 0)) { /* > 2.0 returns OK on QUIT and read() should be issued once more * to know the descriptor is at EOF. */ test_cond(strcasecmp(reply->str,"OK") == 0 && redictGetReply(c,&_reply) == REDICT_ERR); freeReplyObject(reply); } else { test_cond(reply == NULL); } #ifndef _WIN32 /* On 2.0, QUIT will cause the connection to be closed immediately and * the read(2) for the reply on QUIT will set the error to EOF. * On >2.0, QUIT will return with OK and another read(2) needed to be * issued to find out the socket was closed by the server. In both * conditions, the error will be set to EOF. */ assert(c->err == REDICT_ERR_EOF && strcmp(c->errstr,"Server closed the connection") == 0); #endif redictFree(c); c = do_connect(config); test("Returns I/O error on socket timeout: "); struct timeval tv = { 0, 1000 }; assert(redictSetTimeout(c,tv) == REDICT_OK); int respcode = redictGetReply(c,&_reply); #ifndef _WIN32 test_cond(respcode == REDICT_ERR && c->err == REDICT_ERR_IO && errno == EAGAIN); #else test_cond(respcode == REDICT_ERR && c->err == REDICT_ERR_TIMEOUT); #endif redictFree(c); } static void test_invalid_timeout_errors(struct config config) { redictContext *c = NULL; test("Set error when an invalid timeout usec value is used during connect: "); config.connect_timeout.tv_sec = 0; config.connect_timeout.tv_usec = 10000001; if (config.type == CONN_TCP || config.type == CONN_SSL) { c = redictConnectWithTimeout(config.tcp.host, config.tcp.port, config.connect_timeout); } else if(config.type == CONN_UNIX) { c = redictConnectUnixWithTimeout(config.unix_sock.path, config.connect_timeout); } else { redictTestPanic("Unknown connection type!"); } test_cond(c != NULL && c->err == REDICT_ERR_IO && strcmp(c->errstr, "Invalid timeout specified") == 0); redictFree(c); test("Set error when an invalid timeout sec value is used during connect: "); config.connect_timeout.tv_sec = (((LONG_MAX) - 999) / 1000) + 1; config.connect_timeout.tv_usec = 0; if (config.type == CONN_TCP || config.type == CONN_SSL) { c = redictConnectWithTimeout(config.tcp.host, config.tcp.port, config.connect_timeout); } else if(config.type == CONN_UNIX) { c = redictConnectUnixWithTimeout(config.unix_sock.path, config.connect_timeout); } else { redictTestPanic("Unknown connection type!"); } test_cond(c != NULL && c->err == REDICT_ERR_IO && strcmp(c->errstr, "Invalid timeout specified") == 0); redictFree(c); } /* Wrap malloc to abort on failure so OOM checks don't make the test logic * harder to follow. */ void *hi_malloc_safe(size_t size) { void *ptr = hi_malloc(size); if (ptr == NULL) { fprintf(stderr, "Error: Out of memory\n"); exit(-1); } return ptr; } static void test_throughput(struct config config) { redictContext *c = do_connect(config); redictReply **replies; int i, num; long long t1, t2; test("Throughput:\n"); for (i = 0; i < 500; i++) freeReplyObject(redictCommand(c,"LPUSH mylist foo")); num = 1000; replies = hi_malloc_safe(sizeof(redictReply*)*num); t1 = usec(); for (i = 0; i < num; i++) { replies[i] = redictCommand(c,"PING"); assert(replies[i] != NULL && replies[i]->type == REDICT_REPLY_STATUS); } t2 = usec(); for (i = 0; i < num; i++) freeReplyObject(replies[i]); hi_free(replies); printf("\t(%dx PING: %.3fs)\n", num, (t2-t1)/1000000.0); replies = hi_malloc_safe(sizeof(redictReply*)*num); t1 = usec(); for (i = 0; i < num; i++) { replies[i] = redictCommand(c,"LRANGE mylist 0 499"); assert(replies[i] != NULL && replies[i]->type == REDICT_REPLY_ARRAY); assert(replies[i] != NULL && replies[i]->elements == 500); } t2 = usec(); for (i = 0; i < num; i++) freeReplyObject(replies[i]); hi_free(replies); printf("\t(%dx LRANGE with 500 elements: %.3fs)\n", num, (t2-t1)/1000000.0); replies = hi_malloc_safe(sizeof(redictReply*)*num); t1 = usec(); for (i = 0; i < num; i++) { replies[i] = redictCommand(c, "INCRBY incrkey %d", 1000000); assert(replies[i] != NULL && replies[i]->type == REDICT_REPLY_INTEGER); } t2 = usec(); for (i = 0; i < num; i++) freeReplyObject(replies[i]); hi_free(replies); printf("\t(%dx INCRBY: %.3fs)\n", num, (t2-t1)/1000000.0); num = 10000; replies = hi_malloc_safe(sizeof(redictReply*)*num); for (i = 0; i < num; i++) redictAppendCommand(c,"PING"); t1 = usec(); for (i = 0; i < num; i++) { assert(redictGetReply(c, (void*)&replies[i]) == REDICT_OK); assert(replies[i] != NULL && replies[i]->type == REDICT_REPLY_STATUS); } t2 = usec(); for (i = 0; i < num; i++) freeReplyObject(replies[i]); hi_free(replies); printf("\t(%dx PING (pipelined): %.3fs)\n", num, (t2-t1)/1000000.0); replies = hi_malloc_safe(sizeof(redictReply*)*num); for (i = 0; i < num; i++) redictAppendCommand(c,"LRANGE mylist 0 499"); t1 = usec(); for (i = 0; i < num; i++) { assert(redictGetReply(c, (void*)&replies[i]) == REDICT_OK); assert(replies[i] != NULL && replies[i]->type == REDICT_REPLY_ARRAY); assert(replies[i] != NULL && replies[i]->elements == 500); } t2 = usec(); for (i = 0; i < num; i++) freeReplyObject(replies[i]); hi_free(replies); printf("\t(%dx LRANGE with 500 elements (pipelined): %.3fs)\n", num, (t2-t1)/1000000.0); replies = hi_malloc_safe(sizeof(redictReply*)*num); for (i = 0; i < num; i++) redictAppendCommand(c,"INCRBY incrkey %d", 1000000); t1 = usec(); for (i = 0; i < num; i++) { assert(redictGetReply(c, (void*)&replies[i]) == REDICT_OK); assert(replies[i] != NULL && replies[i]->type == REDICT_REPLY_INTEGER); } t2 = usec(); for (i = 0; i < num; i++) freeReplyObject(replies[i]); hi_free(replies); printf("\t(%dx INCRBY (pipelined): %.3fs)\n", num, (t2-t1)/1000000.0); disconnect(c, 0); } // static long __test_callback_flags = 0; // static void __test_callback(redictContext *c, void *privdata) { // ((void)c); // /* Shift to detect execution order */ // __test_callback_flags <<= 8; // __test_callback_flags |= (long)privdata; // } // // static void __test_reply_callback(redictContext *c, redictReply *reply, void *privdata) { // ((void)c); // /* Shift to detect execution order */ // __test_callback_flags <<= 8; // __test_callback_flags |= (long)privdata; // if (reply) freeReplyObject(reply); // } // // static redictContext *__connect_nonblock() { // /* Reset callback flags */ // __test_callback_flags = 0; // return redictConnectNonBlock("127.0.0.1", port, NULL); // } // // static void test_nonblocking_connection() { // redictContext *c; // int wdone = 0; // // test("Calls command callback when command is issued: "); // c = __connect_nonblock(); // redictSetCommandCallback(c,__test_callback,(void*)1); // redictCommand(c,"PING"); // test_cond(__test_callback_flags == 1); // redictFree(c); // // test("Calls disconnect callback on redictDisconnect: "); // c = __connect_nonblock(); // redictSetDisconnectCallback(c,__test_callback,(void*)2); // redictDisconnect(c); // test_cond(__test_callback_flags == 2); // redictFree(c); // // test("Calls disconnect callback and free callback on redictFree: "); // c = __connect_nonblock(); // redictSetDisconnectCallback(c,__test_callback,(void*)2); // redictSetFreeCallback(c,__test_callback,(void*)4); // redictFree(c); // test_cond(__test_callback_flags == ((2 << 8) | 4)); // // test("redictBufferWrite against empty write buffer: "); // c = __connect_nonblock(); // test_cond(redictBufferWrite(c,&wdone) == REDICT_OK && wdone == 1); // redictFree(c); // // test("redictBufferWrite against not yet connected fd: "); // c = __connect_nonblock(); // redictCommand(c,"PING"); // test_cond(redictBufferWrite(c,NULL) == REDICT_ERR && // strncmp(c->error,"write:",6) == 0); // redictFree(c); // // test("redictBufferWrite against closed fd: "); // c = __connect_nonblock(); // redictCommand(c,"PING"); // redictDisconnect(c); // test_cond(redictBufferWrite(c,NULL) == REDICT_ERR && // strncmp(c->error,"write:",6) == 0); // redictFree(c); // // test("Process callbacks in the right sequence: "); // c = __connect_nonblock(); // redictCommandWithCallback(c,__test_reply_callback,(void*)1,"PING"); // redictCommandWithCallback(c,__test_reply_callback,(void*)2,"PING"); // redictCommandWithCallback(c,__test_reply_callback,(void*)3,"PING"); // // /* Write output buffer */ // wdone = 0; // while(!wdone) { // usleep(500); // redictBufferWrite(c,&wdone); // } // // /* Read until at least one callback is executed (the 3 replies will // * arrive in a single packet, causing all callbacks to be executed in // * a single pass). */ // while(__test_callback_flags == 0) { // assert(redictBufferRead(c) == REDICT_OK); // redictProcessCallbacks(c); // } // test_cond(__test_callback_flags == 0x010203); // redictFree(c); // // test("redictDisconnect executes pending callbacks with NULL reply: "); // c = __connect_nonblock(); // redictSetDisconnectCallback(c,__test_callback,(void*)1); // redictCommandWithCallback(c,__test_reply_callback,(void*)2,"PING"); // redictDisconnect(c); // test_cond(__test_callback_flags == 0x0201); // redictFree(c); // } #ifdef HIREDICT_TEST_ASYNC #pragma GCC diagnostic ignored "-Woverlength-strings" /* required on gcc 4.8.x due to assert statements */ struct event_base *base; typedef struct TestState { redictOptions *options; int checkpoint; int resp3; int disconnect; } TestState; /* Helper to disconnect and stop event loop */ void async_disconnect(redictAsyncContext *ac) { redictAsyncDisconnect(ac); event_base_loopbreak(base); } /* Testcase timeout, will trigger a failure */ void timeout_cb(int fd, short event, void *arg) { (void) fd; (void) event; (void) arg; printf("Timeout in async testing!\n"); exit(1); } /* Unexpected call, will trigger a failure */ void unexpected_cb(redictAsyncContext *ac, void *r, void *privdata) { (void) ac; (void) r; printf("Unexpected call: %s\n",(char*)privdata); exit(1); } /* Helper function to publish a message via own client. */ void publish_msg(redictOptions *options, const char* channel, const char* msg) { redictContext *c = redictConnectWithOptions(options); assert(c != NULL); redictReply *reply = redictCommand(c,"PUBLISH %s %s",channel,msg); assert(reply->type == REDICT_REPLY_INTEGER && reply->integer == 1); freeReplyObject(reply); disconnect(c, 0); } /* Expect a reply of type INTEGER */ void integer_cb(redictAsyncContext *ac, void *r, void *privdata) { redictReply *reply = r; TestState *state = privdata; assert(reply != NULL && reply->type == REDICT_REPLY_INTEGER); state->checkpoint++; if (state->disconnect) async_disconnect(ac); } /* Subscribe callback for test_pubsub_handling and test_pubsub_handling_resp3: * - a published message triggers an unsubscribe * - a command is sent before the unsubscribe response is received. */ void subscribe_cb(redictAsyncContext *ac, void *r, void *privdata) { redictReply *reply = r; TestState *state = privdata; assert(reply != NULL && reply->type == (state->resp3 ? REDICT_REPLY_PUSH : REDICT_REPLY_ARRAY) && reply->elements == 3); if (strcmp(reply->element[0]->str,"subscribe") == 0) { assert(strcmp(reply->element[1]->str,"mychannel") == 0 && reply->element[2]->str == NULL); publish_msg(state->options,"mychannel","Hello!"); } else if (strcmp(reply->element[0]->str,"message") == 0) { assert(strcmp(reply->element[1]->str,"mychannel") == 0 && strcmp(reply->element[2]->str,"Hello!") == 0); state->checkpoint++; /* Unsubscribe after receiving the published message. Send unsubscribe * which should call the callback registered during subscribe */ redictAsyncCommand(ac,unexpected_cb, (void*)"unsubscribe should call subscribe_cb()", "unsubscribe"); /* Send a regular command after unsubscribing, then disconnect */ state->disconnect = 1; redictAsyncCommand(ac,integer_cb,state,"LPUSH mylist foo"); } else if (strcmp(reply->element[0]->str,"unsubscribe") == 0) { assert(strcmp(reply->element[1]->str,"mychannel") == 0 && reply->element[2]->str == NULL); } else { printf("Unexpected pubsub command: %s\n", reply->element[0]->str); exit(1); } } /* Expect a reply of type ARRAY */ void array_cb(redictAsyncContext *ac, void *r, void *privdata) { redictReply *reply = r; TestState *state = privdata; assert(reply != NULL && reply->type == REDICT_REPLY_ARRAY); state->checkpoint++; if (state->disconnect) async_disconnect(ac); } /* Expect a NULL reply */ void null_cb(redictAsyncContext *ac, void *r, void *privdata) { (void) ac; assert(r == NULL); TestState *state = privdata; state->checkpoint++; } static void test_pubsub_handling(struct config config) { test("Subscribe, handle published message and unsubscribe: "); /* Setup event dispatcher with a testcase timeout */ base = event_base_new(); struct event *timeout = evtimer_new(base, timeout_cb, NULL); assert(timeout != NULL); evtimer_assign(timeout,base,timeout_cb,NULL); struct timeval timeout_tv = {.tv_sec = 10}; evtimer_add(timeout, &timeout_tv); /* Connect */ redictOptions options = get_redict_tcp_options(config); redictAsyncContext *ac = redictAsyncConnectWithOptions(&options); assert(ac != NULL && ac->err == 0); redictLibeventAttach(ac,base); /* Start subscribe */ TestState state = {.options = &options}; redictAsyncCommand(ac,subscribe_cb,&state,"subscribe mychannel"); /* Make sure non-subscribe commands are handled */ redictAsyncCommand(ac,array_cb,&state,"PING"); /* Start event dispatching loop */ test_cond(event_base_dispatch(base) == 0); event_free(timeout); event_base_free(base); /* Verify test checkpoints */ assert(state.checkpoint == 3); } /* Unexpected push message, will trigger a failure */ void unexpected_push_cb(redictAsyncContext *ac, void *r) { (void) ac; (void) r; printf("Unexpected call to the PUSH callback!\n"); exit(1); } static void test_pubsub_handling_resp3(struct config config) { test("Subscribe, handle published message and unsubscribe using RESP3: "); /* Setup event dispatcher with a testcase timeout */ base = event_base_new(); struct event *timeout = evtimer_new(base, timeout_cb, NULL); assert(timeout != NULL); evtimer_assign(timeout,base,timeout_cb,NULL); struct timeval timeout_tv = {.tv_sec = 10}; evtimer_add(timeout, &timeout_tv); /* Connect */ redictOptions options = get_redict_tcp_options(config); redictAsyncContext *ac = redictAsyncConnectWithOptions(&options); assert(ac != NULL && ac->err == 0); redictLibeventAttach(ac,base); /* Not expecting any push messages in this test */ redictAsyncSetPushCallback(ac, unexpected_push_cb); /* Switch protocol */ redictAsyncCommand(ac,NULL,NULL,"HELLO 3"); /* Start subscribe */ TestState state = {.options = &options, .resp3 = 1}; redictAsyncCommand(ac,subscribe_cb,&state,"subscribe mychannel"); /* Make sure non-subscribe commands are handled in RESP3 */ redictAsyncCommand(ac,integer_cb,&state,"LPUSH mylist foo"); redictAsyncCommand(ac,integer_cb,&state,"LPUSH mylist foo"); redictAsyncCommand(ac,integer_cb,&state,"LPUSH mylist foo"); /* Handle an array with 3 elements as a non-subscribe command */ redictAsyncCommand(ac,array_cb,&state,"LRANGE mylist 0 2"); /* Start event dispatching loop */ test_cond(event_base_dispatch(base) == 0); event_free(timeout); event_base_free(base); /* Verify test checkpoints */ assert(state.checkpoint == 6); } /* Subscribe callback for test_command_timeout_during_pubsub: * - a subscribe response triggers a published message * - the published message triggers a command that times out * - the command timeout triggers a disconnect */ void subscribe_with_timeout_cb(redictAsyncContext *ac, void *r, void *privdata) { redictReply *reply = r; TestState *state = privdata; /* The non-clean disconnect should trigger the * subscription callback with a NULL reply. */ if (reply == NULL) { state->checkpoint++; event_base_loopbreak(base); return; } assert(reply->type == (state->resp3 ? REDICT_REPLY_PUSH : REDICT_REPLY_ARRAY) && reply->elements == 3); if (strcmp(reply->element[0]->str,"subscribe") == 0) { assert(strcmp(reply->element[1]->str,"mychannel") == 0 && reply->element[2]->str == NULL); publish_msg(state->options,"mychannel","Hello!"); state->checkpoint++; } else if (strcmp(reply->element[0]->str,"message") == 0) { assert(strcmp(reply->element[1]->str,"mychannel") == 0 && strcmp(reply->element[2]->str,"Hello!") == 0); state->checkpoint++; /* Send a command that will trigger a timeout */ redictAsyncCommand(ac,null_cb,state,"DEBUG SLEEP 3"); redictAsyncCommand(ac,null_cb,state,"LPUSH mylist foo"); } else { printf("Unexpected pubsub command: %s\n", reply->element[0]->str); exit(1); } } static void test_command_timeout_during_pubsub(struct config config) { test("Command timeout during Pub/Sub: "); /* Setup event dispatcher with a testcase timeout */ base = event_base_new(); struct event *timeout = evtimer_new(base,timeout_cb,NULL); assert(timeout != NULL); evtimer_assign(timeout,base,timeout_cb,NULL); struct timeval timeout_tv = {.tv_sec = 10}; evtimer_add(timeout,&timeout_tv); /* Connect */ redictOptions options = get_redict_tcp_options(config); redictAsyncContext *ac = redictAsyncConnectWithOptions(&options); assert(ac != NULL && ac->err == 0); redictLibeventAttach(ac,base); /* Configure a command timout */ struct timeval command_timeout = {.tv_sec = 2}; redictAsyncSetTimeout(ac,command_timeout); /* Not expecting any push messages in this test */ redictAsyncSetPushCallback(ac,unexpected_push_cb); /* Switch protocol */ redictAsyncCommand(ac,NULL,NULL,"HELLO 3"); /* Start subscribe */ TestState state = {.options = &options, .resp3 = 1}; redictAsyncCommand(ac,subscribe_with_timeout_cb,&state,"subscribe mychannel"); /* Start event dispatching loop */ assert(event_base_dispatch(base) == 0); event_free(timeout); event_base_free(base); /* Verify test checkpoints */ test_cond(state.checkpoint == 5); } /* Subscribe callback for test_pubsub_multiple_channels */ void subscribe_channel_a_cb(redictAsyncContext *ac, void *r, void *privdata) { redictReply *reply = r; TestState *state = privdata; assert(reply != NULL && reply->type == REDICT_REPLY_ARRAY && reply->elements == 3); if (strcmp(reply->element[0]->str,"subscribe") == 0) { assert(strcmp(reply->element[1]->str,"A") == 0); publish_msg(state->options,"A","Hello!"); state->checkpoint++; } else if (strcmp(reply->element[0]->str,"message") == 0) { assert(strcmp(reply->element[1]->str,"A") == 0 && strcmp(reply->element[2]->str,"Hello!") == 0); state->checkpoint++; /* Unsubscribe to channels, including channel X & Z which we don't subscribe to */ redictAsyncCommand(ac,unexpected_cb, (void*)"unsubscribe should not call unexpected_cb()", "unsubscribe B X A A Z"); /* Unsubscribe to patterns, none which we subscribe to */ redictAsyncCommand(ac,unexpected_cb, (void*)"punsubscribe should not call unexpected_cb()", "punsubscribe"); /* Send a regular command after unsubscribing, then disconnect */ state->disconnect = 1; redictAsyncCommand(ac,integer_cb,state,"LPUSH mylist foo"); } else if (strcmp(reply->element[0]->str,"unsubscribe") == 0) { assert(strcmp(reply->element[1]->str,"A") == 0); state->checkpoint++; } else { printf("Unexpected pubsub command: %s\n", reply->element[0]->str); exit(1); } } /* Subscribe callback for test_pubsub_multiple_channels */ void subscribe_channel_b_cb(redictAsyncContext *ac, void *r, void *privdata) { redictReply *reply = r; TestState *state = privdata; (void)ac; assert(reply != NULL && reply->type == REDICT_REPLY_ARRAY && reply->elements == 3); if (strcmp(reply->element[0]->str,"subscribe") == 0) { assert(strcmp(reply->element[1]->str,"B") == 0); state->checkpoint++; } else if (strcmp(reply->element[0]->str,"unsubscribe") == 0) { assert(strcmp(reply->element[1]->str,"B") == 0); state->checkpoint++; } else { printf("Unexpected pubsub command: %s\n", reply->element[0]->str); exit(1); } } /* Test handling of multiple channels * - subscribe to channel A and B * - a published message on A triggers an unsubscribe of channel B, X, A and Z * where channel X and Z are not subscribed to. * - the published message also triggers an unsubscribe to patterns. Since no * pattern is subscribed to the responded pattern element type is NIL. * - a command sent after unsubscribe triggers a disconnect */ static void test_pubsub_multiple_channels(struct config config) { test("Subscribe to multiple channels: "); /* Setup event dispatcher with a testcase timeout */ base = event_base_new(); struct event *timeout = evtimer_new(base,timeout_cb,NULL); assert(timeout != NULL); evtimer_assign(timeout,base,timeout_cb,NULL); struct timeval timeout_tv = {.tv_sec = 10}; evtimer_add(timeout,&timeout_tv); /* Connect */ redictOptions options = get_redict_tcp_options(config); redictAsyncContext *ac = redictAsyncConnectWithOptions(&options); assert(ac != NULL && ac->err == 0); redictLibeventAttach(ac,base); /* Not expecting any push messages in this test */ redictAsyncSetPushCallback(ac,unexpected_push_cb); /* Start subscribing to two channels */ TestState state = {.options = &options}; redictAsyncCommand(ac,subscribe_channel_a_cb,&state,"subscribe A"); redictAsyncCommand(ac,subscribe_channel_b_cb,&state,"subscribe B"); /* Start event dispatching loop */ assert(event_base_dispatch(base) == 0); event_free(timeout); event_base_free(base); /* Verify test checkpoints */ test_cond(state.checkpoint == 6); } /* Command callback for test_monitor() */ void monitor_cb(redictAsyncContext *ac, void *r, void *privdata) { redictReply *reply = r; TestState *state = privdata; /* NULL reply is received when BYE triggers a disconnect. */ if (reply == NULL) { event_base_loopbreak(base); return; } assert(reply != NULL && reply->type == REDICT_REPLY_STATUS); state->checkpoint++; if (state->checkpoint == 1) { /* Response from MONITOR */ redictContext *c = redictConnectWithOptions(state->options); assert(c != NULL); redictReply *reply = redictCommand(c,"SET first 1"); assert(reply->type == REDICT_REPLY_STATUS); freeReplyObject(reply); redictFree(c); } else if (state->checkpoint == 2) { /* Response for monitored command 'SET first 1' */ assert(strstr(reply->str,"first") != NULL); redictContext *c = redictConnectWithOptions(state->options); assert(c != NULL); redictReply *reply = redictCommand(c,"SET second 2"); assert(reply->type == REDICT_REPLY_STATUS); freeReplyObject(reply); redictFree(c); } else if (state->checkpoint == 3) { /* Response for monitored command 'SET second 2' */ assert(strstr(reply->str,"second") != NULL); /* Send QUIT to disconnect */ redictAsyncCommand(ac,NULL,NULL,"QUIT"); } } /* Test handling of the monitor command * - sends MONITOR to enable monitoring. * - sends SET commands via separate clients to be monitored. * - sends QUIT to stop monitoring and disconnect. */ static void test_monitor(struct config config) { test("Enable monitoring: "); /* Setup event dispatcher with a testcase timeout */ base = event_base_new(); struct event *timeout = evtimer_new(base, timeout_cb, NULL); assert(timeout != NULL); evtimer_assign(timeout,base,timeout_cb,NULL); struct timeval timeout_tv = {.tv_sec = 10}; evtimer_add(timeout, &timeout_tv); /* Connect */ redictOptions options = get_redict_tcp_options(config); redictAsyncContext *ac = redictAsyncConnectWithOptions(&options); assert(ac != NULL && ac->err == 0); redictLibeventAttach(ac,base); /* Not expecting any push messages in this test */ redictAsyncSetPushCallback(ac,unexpected_push_cb); /* Start monitor */ TestState state = {.options = &options}; redictAsyncCommand(ac,monitor_cb,&state,"monitor"); /* Start event dispatching loop */ test_cond(event_base_dispatch(base) == 0); event_free(timeout); event_base_free(base); /* Verify test checkpoints */ assert(state.checkpoint == 3); } #endif /* HIREDICT_TEST_ASYNC */ /* tests for async api using polling adapter, requires no extra libraries*/ /* enum for the test cases, the callbacks have different logic based on them */ typedef enum astest_no { ASTEST_CONNECT=0, ASTEST_CONN_TIMEOUT, ASTEST_PINGPONG, ASTEST_PINGPONG_TIMEOUT, ASTEST_ISSUE_931, ASTEST_ISSUE_931_PING }astest_no; /* a static context for the async tests */ struct _astest { redictAsyncContext *ac; astest_no testno; int counter; int connects; int connect_status; int disconnects; int pongs; int disconnect_status; int connected; int err; char errstr[256]; }; static struct _astest astest; /* async callbacks */ static void asCleanup(void* data) { struct _astest *t = (struct _astest *)data; t->ac = NULL; } static void commandCallback(struct redictAsyncContext *ac, void* _reply, void* _privdata); static void connectCallback(redictAsyncContext *c, int status) { struct _astest *t = (struct _astest *)c->data; assert(t == &astest); assert(t->connects == 0); t->err = c->err; strcpy(t->errstr, c->errstr); t->connects++; t->connect_status = status; t->connected = status == REDICT_OK ? 1 : -1; if (t->testno == ASTEST_ISSUE_931) { /* disconnect again */ redictAsyncDisconnect(c); } else if (t->testno == ASTEST_ISSUE_931_PING) { redictAsyncCommand(c, commandCallback, NULL, "PING"); } } static void disconnectCallback(const redictAsyncContext *c, int status) { assert(c->data == (void*)&astest); assert(astest.disconnects == 0); astest.err = c->err; strcpy(astest.errstr, c->errstr); astest.disconnects++; astest.disconnect_status = status; astest.connected = 0; } static void commandCallback(struct redictAsyncContext *ac, void* _reply, void* _privdata) { redictReply *reply = (redictReply*)_reply; struct _astest *t = (struct _astest *)ac->data; assert(t == &astest); (void)_privdata; t->err = ac->err; strcpy(t->errstr, ac->errstr); t->counter++; if (t->testno == ASTEST_PINGPONG ||t->testno == ASTEST_ISSUE_931_PING) { assert(reply != NULL && reply->type == REDICT_REPLY_STATUS && strcmp(reply->str, "PONG") == 0); t->pongs++; redictAsyncFree(ac); } if (t->testno == ASTEST_PINGPONG_TIMEOUT) { /* two ping pongs */ assert(reply != NULL && reply->type == REDICT_REPLY_STATUS && strcmp(reply->str, "PONG") == 0); t->pongs++; if (t->counter == 1) { int status = redictAsyncCommand(ac, commandCallback, NULL, "PING"); assert(status == REDICT_OK); } else { redictAsyncFree(ac); } } } static redictAsyncContext *do_aconnect(struct config config, astest_no testno) { redictOptions options = {0}; memset(&astest, 0, sizeof(astest)); astest.testno = testno; astest.connect_status = astest.disconnect_status = -2; if (config.type == CONN_TCP) { options.type = REDICT_CONN_TCP; options.connect_timeout = &config.connect_timeout; REDICT_OPTIONS_SET_TCP(&options, config.tcp.host, config.tcp.port); } else if (config.type == CONN_SSL) { options.type = REDICT_CONN_TCP; options.connect_timeout = &config.connect_timeout; REDICT_OPTIONS_SET_TCP(&options, config.ssl.host, config.ssl.port); } else if (config.type == CONN_UNIX) { options.type = REDICT_CONN_UNIX; options.endpoint.unix_socket = config.unix_sock.path; } else if (config.type == CONN_FD) { options.type = REDICT_CONN_USERFD; /* Create a dummy connection just to get an fd to inherit */ redictContext *dummy_ctx = redictConnectUnix(config.unix_sock.path); if (dummy_ctx) { redictFD fd = disconnect(dummy_ctx, 1); printf("Connecting to inherited fd %d\n", (int)fd); options.endpoint.fd = fd; } } redictAsyncContext *c = redictAsyncConnectWithOptions(&options); assert(c); astest.ac = c; c->data = &astest; c->dataCleanup = asCleanup; redictPollAttach(c); redictAsyncSetConnectCallbackNC(c, connectCallback); redictAsyncSetDisconnectCallback(c, disconnectCallback); return c; } static void as_printerr(void) { printf("Async err %d : %s\n", astest.err, astest.errstr); } #define ASASSERT(e) do { \ if (!(e)) \ as_printerr(); \ assert(e); \ } while (0); static void test_async_polling(struct config config) { int status; redictAsyncContext *c; struct config defaultconfig = config; test("Async connect: "); c = do_aconnect(config, ASTEST_CONNECT); assert(c); while(astest.connected == 0) redictPollTick(c, 0.1); assert(astest.connects == 1); ASASSERT(astest.connect_status == REDICT_OK); assert(astest.disconnects == 0); test_cond(astest.connected == 1); test("Async free after connect: "); assert(astest.ac != NULL); redictAsyncFree(c); assert(astest.disconnects == 1); assert(astest.ac == NULL); test_cond(astest.disconnect_status == REDICT_OK); if (config.type == CONN_TCP || config.type == CONN_SSL) { /* timeout can only be simulated with network */ test("Async connect timeout: "); config.tcp.host = "192.168.254.254"; /* blackhole ip */ config.connect_timeout.tv_usec = 100000; c = do_aconnect(config, ASTEST_CONN_TIMEOUT); assert(c); assert(c->err == 0); while(astest.connected == 0) redictPollTick(c, 0.1); assert(astest.connected == -1); /* * freeing should not be done, clearing should have happened. *redictAsyncFree(c); */ assert(astest.ac == NULL); test_cond(astest.connect_status == REDICT_ERR); config = defaultconfig; } /* Test a ping/pong after connection */ test("Async PING/PONG: "); c = do_aconnect(config, ASTEST_PINGPONG); while(astest.connected == 0) redictPollTick(c, 0.1); status = redictAsyncCommand(c, commandCallback, NULL, "PING"); assert(status == REDICT_OK); while(astest.ac) redictPollTick(c, 0.1); test_cond(astest.pongs == 1); /* Test a ping/pong after connection that didn't time out. * see https://github.com/redict/hiredict/issues/945 */ if (config.type == CONN_TCP || config.type == CONN_SSL) { test("Async PING/PONG after connect timeout: "); config.connect_timeout.tv_usec = 10000; /* 10ms */ c = do_aconnect(config, ASTEST_PINGPONG_TIMEOUT); while(astest.connected == 0) redictPollTick(c, 0.1); /* sleep 0.1 s, allowing old timeout to arrive */ millisleep(10); status = redictAsyncCommand(c, commandCallback, NULL, "PING"); assert(status == REDICT_OK); while(astest.ac) redictPollTick(c, 0.1); test_cond(astest.pongs == 2); config = defaultconfig; } /* Test disconnect from an on_connect callback * see https://github.com/redict/hiredict/issues/931 */ test("Disconnect from onConnected callback (Issue #931): "); c = do_aconnect(config, ASTEST_ISSUE_931); while(astest.disconnects == 0) redictPollTick(c, 0.1); assert(astest.connected == 0); assert(astest.connects == 1); test_cond(astest.disconnects == 1); /* Test ping/pong from an on_connect callback * see https://github.com/redict/hiredict/issues/931 */ test("Ping/Pong from onConnected callback (Issue #931): "); c = do_aconnect(config, ASTEST_ISSUE_931_PING); /* connect callback issues ping, response callback destroys context */ while(astest.ac) redictPollTick(c, 0.1); assert(astest.connected == 0); assert(astest.connects == 1); assert(astest.disconnects == 1); test_cond(astest.pongs == 1); } /* End of Async polling_adapter driven tests */ int main(int argc, char **argv) { struct config cfg = { .tcp = { .host = "127.0.0.1", .port = 6379 }, .unix_sock = { .path = "/tmp/redict.sock" } }; int throughput = 1; int test_inherit_fd = 1; int skips_as_fails = 0; int test_unix_socket; /* Parse command line options. */ argv++; argc--; while (argc) { if (argc >= 2 && !strcmp(argv[0],"-h")) { argv++; argc--; cfg.tcp.host = argv[0]; } else if (argc >= 2 && !strcmp(argv[0],"-p")) { argv++; argc--; cfg.tcp.port = atoi(argv[0]); } else if (argc >= 2 && !strcmp(argv[0],"-s")) { argv++; argc--; cfg.unix_sock.path = argv[0]; } else if (argc >= 1 && !strcmp(argv[0],"--skip-throughput")) { throughput = 0; } else if (argc >= 1 && !strcmp(argv[0],"--skip-inherit-fd")) { test_inherit_fd = 0; } else if (argc >= 1 && !strcmp(argv[0],"--skips-as-fails")) { skips_as_fails = 1; #ifdef HIREDICT_TEST_SSL } else if (argc >= 2 && !strcmp(argv[0],"--ssl-port")) { argv++; argc--; cfg.ssl.port = atoi(argv[0]); } else if (argc >= 2 && !strcmp(argv[0],"--ssl-host")) { argv++; argc--; cfg.ssl.host = argv[0]; } else if (argc >= 2 && !strcmp(argv[0],"--ssl-ca-cert")) { argv++; argc--; cfg.ssl.ca_cert = argv[0]; } else if (argc >= 2 && !strcmp(argv[0],"--ssl-cert")) { argv++; argc--; cfg.ssl.cert = argv[0]; } else if (argc >= 2 && !strcmp(argv[0],"--ssl-key")) { argv++; argc--; cfg.ssl.key = argv[0]; #endif } else { fprintf(stderr, "Invalid argument: %s\n", argv[0]); exit(1); } argv++; argc--; } #ifndef _WIN32 /* Ignore broken pipe signal (for I/O error tests). */ signal(SIGPIPE, SIG_IGN); test_unix_socket = access(cfg.unix_sock.path, F_OK) == 0; #else /* Unix sockets don't exist in Windows */ test_unix_socket = 0; #endif test_allocator_injection(); test_format_commands(); test_reply_reader(); test_blocking_connection_errors(); test_free_null(); printf("\nTesting against TCP connection (%s:%d):\n", cfg.tcp.host, cfg.tcp.port); cfg.type = CONN_TCP; test_blocking_connection(cfg); test_blocking_connection_timeouts(cfg); test_blocking_io_errors(cfg); test_invalid_timeout_errors(cfg); test_append_formatted_commands(cfg); test_tcp_options(cfg); if (throughput) test_throughput(cfg); printf("\nTesting against Unix socket connection (%s): ", cfg.unix_sock.path); if (test_unix_socket) { printf("\n"); cfg.type = CONN_UNIX; test_blocking_connection(cfg); test_blocking_connection_timeouts(cfg); test_blocking_io_errors(cfg); test_invalid_timeout_errors(cfg); test_unix_keepalive(cfg); if (throughput) test_throughput(cfg); } else { test_skipped(); } #ifdef HIREDICT_TEST_SSL if (cfg.ssl.port && cfg.ssl.host) { redictInitOpenSSL(); _ssl_ctx = redictCreateSSLContext(cfg.ssl.ca_cert, NULL, cfg.ssl.cert, cfg.ssl.key, NULL, NULL); assert(_ssl_ctx != NULL); printf("\nTesting against SSL connection (%s:%d):\n", cfg.ssl.host, cfg.ssl.port); cfg.type = CONN_SSL; test_blocking_connection(cfg); test_blocking_connection_timeouts(cfg); test_blocking_io_errors(cfg); test_invalid_timeout_errors(cfg); test_append_formatted_commands(cfg); if (throughput) test_throughput(cfg); redictFreeSSLContext(_ssl_ctx); _ssl_ctx = NULL; } #endif #ifdef HIREDICT_TEST_ASYNC cfg.type = CONN_TCP; printf("\nTesting asynchronous API against TCP connection (%s:%d):\n", cfg.tcp.host, cfg.tcp.port); cfg.type = CONN_TCP; int major; redictContext *c = do_connect(cfg); get_redict_version(c, &major, NULL); disconnect(c, 0); test_pubsub_handling(cfg); test_pubsub_multiple_channels(cfg); test_monitor(cfg); if (major >= 6) { test_pubsub_handling_resp3(cfg); test_command_timeout_during_pubsub(cfg); } #endif /* HIREDICT_TEST_ASYNC */ cfg.type = CONN_TCP; printf("\nTesting asynchronous API using polling_adapter TCP (%s:%d):\n", cfg.tcp.host, cfg.tcp.port); test_async_polling(cfg); if (test_unix_socket) { cfg.type = CONN_UNIX; printf("\nTesting asynchronous API using polling_adapter UNIX (%s):\n", cfg.unix_sock.path); test_async_polling(cfg); } if (test_inherit_fd) { printf("\nTesting against inherited fd (%s): ", cfg.unix_sock.path); if (test_unix_socket) { printf("\n"); cfg.type = CONN_FD; test_blocking_connection(cfg); } else { test_skipped(); } } if (fails || (skips_as_fails && skips)) { printf("*** %d TESTS FAILED ***\n", fails); if (skips) { printf("*** %d TESTS SKIPPED ***\n", skips); } return 1; } printf("ALL TESTS PASSED (%d skipped)\n", skips); return 0; } hiredict/test.sh000077500000000000000000000056611460333451400142020ustar00rootroot00000000000000#!/bin/sh -ue # SPDX-FileCopyrightText: 2024 Hiredict Contributors # SPDX-FileCopyrightText: 2024 Salvatore Sanfilippo # # SPDX-License-Identifier: BSD-3-Clause # SPDX-License-Identifier: LGPL-3.0-or-later REDICT_SERVER=${REDICT_SERVER:-redict-server} REDICT_PORT=${REDICT_PORT:-56379} REDICT_SSL_PORT=${REDICT_SSL_PORT:-56443} TEST_SSL=${TEST_SSL:-0} SKIPS_AS_FAILS=${SKIPS_AS_FAILS:-0} SSL_TEST_ARGS= SKIPS_ARG=${SKIPS_ARG:-} REDICT_DOCKER=${REDICT_DOCKER:-} tmpdir=$(mktemp -d) PID_FILE=${tmpdir}/hiredict-test-redict.pid SOCK_FILE=${tmpdir}/hiredict-test-redict.sock if [ "$TEST_SSL" = "1" ]; then SSL_CA_CERT=${tmpdir}/ca.crt SSL_CA_KEY=${tmpdir}/ca.key SSL_CERT=${tmpdir}/redict.crt SSL_KEY=${tmpdir}/redict.key openssl genrsa -out ${tmpdir}/ca.key 4096 openssl req \ -x509 -new -nodes -sha256 \ -key ${SSL_CA_KEY} \ -days 3650 \ -subj '/CN=Hiredict Test CA' \ -out ${SSL_CA_CERT} openssl genrsa -out ${SSL_KEY} 2048 openssl req \ -new -sha256 \ -key ${SSL_KEY} \ -subj '/CN=Hiredict Test Cert' | \ openssl x509 \ -req -sha256 \ -CA ${SSL_CA_CERT} \ -CAkey ${SSL_CA_KEY} \ -CAserial ${tmpdir}/ca.txt \ -CAcreateserial \ -days 365 \ -out ${SSL_CERT} SSL_TEST_ARGS="--ssl-host 127.0.0.1 --ssl-port ${REDICT_SSL_PORT} --ssl-ca-cert ${SSL_CA_CERT} --ssl-cert ${SSL_CERT} --ssl-key ${SSL_KEY}" fi cleanup() { if [ -n "${REDICT_DOCKER}" ] ; then docker kill redict-test-server else set +e kill $(cat ${PID_FILE}) fi rm -rf ${tmpdir} } trap cleanup INT TERM EXIT # base config cat > ${tmpdir}/redict.conf <> ${tmpdir}/redict.conf <> ${tmpdir}/redict.conf < * * SPDX-FileCopyrightText: 2024 Hiredict Contributors * SPDX-FileCopyrightText: 2024 Salvatore Sanfilippo * * SPDX-License-Identifier: BSD-3-Clause * SPDX-License-Identifier: LGPL-3.0-or-later * */ #ifndef _WIN32_HELPER_INCLUDE #define _WIN32_HELPER_INCLUDE #ifdef _MSC_VER #include /* for struct timeval */ #ifndef inline #define inline __inline #endif #ifndef strcasecmp #define strcasecmp stricmp #endif #ifndef strncasecmp #define strncasecmp strnicmp #endif #ifndef va_copy #define va_copy(d,s) ((d) = (s)) #endif #ifndef snprintf #define snprintf c99_snprintf __inline int c99_vsnprintf(char* str, size_t size, const char* format, va_list ap) { int count = -1; if (size != 0) count = _vsnprintf_s(str, size, _TRUNCATE, format, ap); if (count == -1) count = _vscprintf(format, ap); return count; } __inline int c99_snprintf(char* str, size_t size, const char* format, ...) { int count; va_list ap; va_start(ap, format); count = c99_vsnprintf(str, size, format, ap); va_end(ap); return count; } #endif #endif /* _MSC_VER */ #ifdef _WIN32 #define strerror_r(errno,buf,len) strerror_s(buf,len,errno) #endif /* _WIN32 */ #endif /* _WIN32_HELPER_INCLUDE */