From 418de21d8feb35303904ec718fcd1387e4699d2f Mon Sep 17 00:00:00 2001 From: Yossi Gottlieb Date: Mon, 14 Feb 2022 13:51:42 +0200 Subject: [PATCH] Squashed 'deps/hiredis/' changes from 00272d669..f8de9a4bd f8de9a4bd Merge pull request #1046 from redis/rockylinux-ci a41c9bc8b CentOS 8 is EOL, switch to RockyLinux be41ed60d Avoid incorrect call to the previous reply's callback (#1040) f2e8010d9 fix building on AIX and SunOS (#1031) e73ab2f23 Add timeout support for libuv adapter (#1016) f2ce5980e Allow sending commands after sending an unsubscribe (#1036) ff860e55d Correction for command timeout during pubsub (#1038) 24d534493 CMakeLists.txt: allow building without a C++ compiler (#872) 4ece9a02e Fix adapters/libevent.h compilation for 64-bit Windows (#937) 799edfaad Don't link with crypto libs if USE_SSL isn't set. f74b08182 Makefile: move SSL options into a block and refine rules f347743b7 Update CMakeLists.txt for more portability (#1005) f2be74802 Fix integer overflow when format command larger than 4GB (#1030) 58aacdac6 Handle array response in parallell with pubsub using RESP3 (#1014) d3384260e Support PING while subscribing (RESP2) (#1027) e3a479e40 FreeBSD build fixes + CI (#1026) da5a4ff36 Add asynchronous test for pubsub using RESP3 (#1012) b5716ee82 Valgrind returns error exit code when errors found (#1011) 1aed21a8c Move to using make directly in Cygwin (#1020) a83f4b890 Correct CMake warning for libevent adapter example c4333203e Remove unused parameter warning in libev adapter 7ad38dc4a Small tweaks of the async tests 4021726a6 Add asynchronous test for pubsub using RESP2 648763c36 Add build options for enabling async tests c98c6994d Correcting the build target `coverage` for enabled SSL (#1009) 30ff8d850 Run SSL tests in CI 4a126e8a9 Add valgrind and CMake to tests b73c2d410 Add Centos8 e9f647384 We should run actions on PRs 6ad4ccf3c Add Cygwin build test 783a3789c Add Windows tests in GitHub actions 0cac8dae1 Switch to GitHub actions fa900ef76 Fix unused variable warning. e489846b7 Minor refactor of CVE-2021-32765 fix. 51c740824 Remove extra comma from cmake var. Or it'll be treated as part of the var name. 632bf0718 Merge branch 'release/v1.0.2' b73128324 Prepare for v1.0.2 GA d4e6f109a Revert erroneous SONAME bump a39824a5d Merge branch 'release/v1.0.1' 8d1bfac46 Prepare for v1.0.1 GA 76a7b1000 Fix for integer/buffer overflow CVE-2021-32765 9eca1f36f Allow to override OPENSSL_PREFIX in Linux 2d9d77518 Don't leak memory if an invalid type is set (#906) f5f31ff9b Added REDIS_NO_AUTO_FREE_REPLIES flag (#962) 5850a8ecd Ensure we curry any connect error to an async context. b6f86f38c Fix README.md 667dbf536 Merge pull request #935 from kristjanvalur/pr5 9bf6c250e Merge pull request #939 from zmartzone/improve_pr_896_ssl_leak 959af9760 Merge pull request #949 from plan-do-break-fix/Typo-corrections 0743f57bb fix(docs): corrects typos in project README 5f4382247 improve SSL leak fix redis/hiredis#896 e06ecf7e4 Ignore timeout callback from a successful connect dfa33e60b Change order independant push logic to not change behavior. 6204182aa Handle the case where an invalidation is sent second. d6a0b192b Merge branch 'reader-updates' 410c24d2a Fix off-by-one error in seekNewline bd7488d27 read: Validate line items prior to checking for object creation callbacks 5f9242a1f read: Remove obsolete comment on nested multi bulk depth limitation 83c145042 read: Add support for the RESP3 bignum type c6646cb19 read: Ensure no invalid '\r' or '\n' in simple status/error strings e43061156 read: Additional validation and test case for RESP3 double c8adea402 redisReply: Fix parent type assertions during double, nil, bool creation ff73f1f9e redisReply: Explicitly list nil and bool cases in freeReplyObject() switch. 0f9251884 test: Add test case for RESP3 set 33c06dd50 test: Add test case for RESP3 map 397fe2630 read: Use memchr() in seekNewline() instead of looping over entire string 81c48a982 test: Add test cases for RESP3 bool 51e693f4f read: Add additional RESP3 bool validation 790b4d3b4 test: Add test cases for RESP3 nil d8899fbc1 read: Add additional RESP3 nil validation 96e8ea611 test: Add test cases for infinite and NaN doubles f913e9b99 read: Fix double validation and infinity parsing 8039c7d26 test: Add test case for doubles 49539fd1a redisReply: Fix - set len in double objects 53a8144c8 Merge pull request #924 from cheese1/master 9390de006 http -> https 7d99b5635 Merge pull request #917 from Nordix/stack-alloc-dict-iter 4bba72103 Handle OOM during async command callback registration 920128a26 Stack allocate dict iterators 297ecbecb Tiny formatting changes + suppress implicit memcpy warning f746a28e7 Removed 2 typecasts 940a04f4d Added fuzzer e4a200040 Merge pull request #896 from ayeganov/bugfix/ssl_leak aefef8987 Free SSL object when redisSSLConnect fails e3f88ebcf Merge pull request #894 from jcohen02/fix/issue893 308ffcab8 Updating SSL connection example 297f6551d Merge pull request #889 from redis/wincert e7dda9785 Formatting f44945a0a Merge pull request #874 from masariello/position-independent-code 74e78498c Merge pull request #888 from michael-grunder/nil-push-invalidation b9b9f446f Fix handling of NIL invalidation messages. acc917548 Merge pull request #885 from gkorland/patch-1 b086f763e clean a warning, remvoe empty else block b47fae4e7 Merge pull request #881 from timgates42/bugfix_typo_terminated f989670e5 docs: Fix simple typo, termined -> terminated 773d6ea8a Copy error to redisAsyncContext on timeout e35300a66 add pdb files to packages for MSVC builds dde6916b4 Add d suffix to debug libraries so that can packaged together with optimized builds (Release, RelWithDebInfo, etc) 3b68b5018 Enable position-independent code 6693863f4 Add support for system CA certificate store on Windows 2a5a57b90 Remove whitespace 1b40ec509 fixed issue with unit test linking on windows with SSL d7b1d21e8 Merge branch 'master' of github.com:redis/hiredis fb0e6c0dd Merge pull request #870 from michael-grunder/cmake-c99 13a35bdb6 Explicitly set c99 in CMake bea137ca9 Merge pull request #868 from michael-grunder/fix-sockaddr-typo bd6f86eb6 Fix sockaddr typo 48696e7e5 Don't use non-installed win32.h helper in examples (#863) faa1c4863 Merge tag 'v1.0.0' 5003906d6 Define a no op assert if we detect NDEBUG (#861) ea063b7cc Use development specific versions in master 04a27f480 We can run SSL tests everywhere except mingw/Windows (#859) 8966a1fc2 Remove extra whitespace (#858) 34b7f7a0f Keep libev's code style (#857) 07c3618ff Add static library target and cpack support REVERT: 00272d669 Rename sds calls so they don't conflict in Redis. git-subtree-dir: deps/hiredis git-subtree-split: f8de9a4bd433791890572f7b9147e685653ddef9 --- .github/workflows/build.yml | 205 ++++++++ .travis.yml | 12 +- CHANGELOG.md | 19 + CMakeLists.txt | 110 ++++- Makefile | 110 +++-- README.md | 41 +- adapters/libev.h | 27 +- adapters/libevent.h | 2 +- adapters/libuv.h | 162 ++++--- alloc.c | 4 + alloc.h | 5 + async.c | 154 +++--- async.h | 2 +- dict.c | 11 +- dict.h | 3 +- examples/CMakeLists.txt | 2 +- examples/example-libuv.c | 37 +- examples/example-push.c | 1 - examples/example-ssl.c | 5 +- examples/example.c | 5 +- fmacros.h | 2 + fuzzing/format_command_fuzzer.c | 57 +++ hiredis.c | 183 +++---- hiredis.h | 28 +- hiredis.targets | 11 + hiredis_ssl.h | 4 +- net.c | 2 +- read.c | 166 ++++--- sds.c | 826 ++++++++++++++++---------------- sds.h | 262 +++++----- sdsalloc.h | 6 +- sdscompat.h | 94 ---- ssl.c | 47 +- test.c | 678 +++++++++++++++++++++++++- 34 files changed, 2239 insertions(+), 1044 deletions(-) create mode 100644 .github/workflows/build.yml create mode 100644 fuzzing/format_command_fuzzer.c create mode 100644 hiredis.targets delete mode 100644 sdscompat.h diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 000000000000..362bc77b7415 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,205 @@ +name: Build and test +on: [push, pull_request] + +jobs: + ubuntu: + name: Ubuntu + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v2 + with: + repository: ${{ env.GITHUB_REPOSITORY }} + ref: ${{ env.GITHUB_HEAD_REF }} + + - name: Install dependencies + run: | + sudo add-apt-repository -y ppa:chris-lea/redis-server + sudo apt-get update + sudo apt-get install -y redis-server valgrind libevent-dev + + - name: Build using cmake + env: + EXTRA_CMAKE_OPTS: -DENABLE_EXAMPLES:BOOL=ON -DENABLE_SSL:BOOL=ON -DENABLE_SSL_TESTS:BOOL=ON -DENABLE_ASYNC_TESTS:BOOL=ON + CFLAGS: -Werror + CXXFLAGS: -Werror + run: mkdir build-ubuntu && cd build-ubuntu && cmake .. + + - name: Build using makefile + run: USE_SSL=1 TEST_ASYNC=1 make + + - name: Run tests + env: + SKIPS_AS_FAILS: 1 + TEST_SSL: 1 + run: $GITHUB_WORKSPACE/test.sh + + # - name: Run tests under valgrind + # env: + # SKIPS_AS_FAILS: 1 + # TEST_PREFIX: valgrind --error-exitcode=99 --track-origins=yes --leak-check=full + # run: $GITHUB_WORKSPACE/test.sh + + centos7: + name: CentOS 7 + runs-on: ubuntu-latest + container: centos:7 + steps: + - name: Checkout code + uses: actions/checkout@v2 + with: + repository: ${{ env.GITHUB_REPOSITORY }} + ref: ${{ env.GITHUB_HEAD_REF }} + + - name: Install dependencies + run: | + yum -y install http://rpms.remirepo.net/enterprise/remi-release-7.rpm + yum -y --enablerepo=remi install redis + yum -y install gcc gcc-c++ make openssl openssl-devel cmake3 valgrind libevent-devel + + - name: Build using cmake + env: + EXTRA_CMAKE_OPTS: -DENABLE_EXAMPLES:BOOL=ON -DENABLE_SSL:BOOL=ON -DENABLE_SSL_TESTS:BOOL=ON -DENABLE_ASYNC_TESTS:BOOL=ON + CFLAGS: -Werror + CXXFLAGS: -Werror + run: mkdir build-centos7 && cd build-centos7 && cmake3 .. + + - name: Build using Makefile + run: USE_SSL=1 TEST_ASYNC=1 make + + - name: Run tests + env: + SKIPS_AS_FAILS: 1 + TEST_SSL: 1 + run: $GITHUB_WORKSPACE/test.sh + + - name: Run tests under valgrind + env: + SKIPS_AS_FAILS: 1 + TEST_SSL: 1 + TEST_PREFIX: valgrind --error-exitcode=99 --track-origins=yes --leak-check=full + run: $GITHUB_WORKSPACE/test.sh + + centos8: + name: RockyLinux 8 + runs-on: ubuntu-latest + container: rockylinux:8 + steps: + - name: Checkout code + uses: actions/checkout@v2 + with: + repository: ${{ env.GITHUB_REPOSITORY }} + ref: ${{ env.GITHUB_HEAD_REF }} + + - name: Install dependencies + run: | + dnf -y install https://rpms.remirepo.net/enterprise/remi-release-8.rpm + dnf -y module install redis:remi-6.0 + dnf -y group install "Development Tools" + dnf -y install openssl-devel cmake valgrind libevent-devel + + - name: Build using cmake + env: + EXTRA_CMAKE_OPTS: -DENABLE_EXAMPLES:BOOL=ON -DENABLE_SSL:BOOL=ON -DENABLE_SSL_TESTS:BOOL=ON -DENABLE_ASYNC_TESTS:BOOL=ON + CFLAGS: -Werror + CXXFLAGS: -Werror + run: mkdir build-centos8 && cd build-centos8 && cmake .. + + - name: Build using Makefile + run: USE_SSL=1 TEST_ASYNC=1 make + + - name: Run tests + env: + SKIPS_AS_FAILS: 1 + TEST_SSL: 1 + run: $GITHUB_WORKSPACE/test.sh + + - name: Run tests under valgrind + env: + SKIPS_AS_FAILS: 1 + TEST_SSL: 1 + TEST_PREFIX: valgrind --error-exitcode=99 --track-origins=yes --leak-check=full + run: $GITHUB_WORKSPACE/test.sh + + freebsd: + runs-on: macos-10.15 + name: FreeBSD + steps: + - name: Checkout code + uses: actions/checkout@v2 + with: + repository: ${{ env.GITHUB_REPOSITORY }} + ref: ${{ env.GITHUB_HEAD_REF }} + + - name: Build in FreeBSD + uses: vmactions/freebsd-vm@v0.1.5 + with: + prepare: pkg install -y gmake cmake + run: | + mkdir build && cd build && cmake .. && make && cd .. + gmake + + macos: + name: macOS + runs-on: macos-latest + steps: + - name: Checkout code + uses: actions/checkout@v2 + with: + repository: ${{ env.GITHUB_REPOSITORY }} + ref: ${{ env.GITHUB_HEAD_REF }} + + - name: Install dependencies + run: | + brew install openssl redis + + - name: Build hiredis + run: USE_SSL=1 make + + - name: Run tests + env: + TEST_SSL: 1 + run: $GITHUB_WORKSPACE/test.sh + + windows: + name: Windows + runs-on: windows-latest + steps: + - name: Checkout code + uses: actions/checkout@v2 + with: + repository: ${{ env.GITHUB_REPOSITORY }} + ref: ${{ env.GITHUB_HEAD_REF }} + + - name: Install dependencies + run: | + choco install -y ninja memurai-developer + + - uses: ilammy/msvc-dev-cmd@v1 + - name: Build hiredis + run: | + mkdir build && cd build + cmake .. -G Ninja -DCMAKE_BUILD_TYPE=Release -DENABLE_EXAMPLES=ON + ninja -v + + - name: Run tests + run: | + ./build/hiredis-test.exe + + - name: Setup cygwin + uses: egor-tensin/setup-cygwin@v3 + with: + platform: x64 + packages: make git gcc-core + + - name: Build in cygwin + env: + HIREDIS_PATH: ${{ github.workspace }} + run: | + build_hiredis() { + cd $(cygpath -u $HIREDIS_PATH) + git clean -xfd + make + } + build_hiredis + shell: C:\tools\cygwin\bin\bash.exe --login --norc -eo pipefail -o igncr '{0}' diff --git a/.travis.yml b/.travis.yml index f9a9460ffb3a..1e9b5569f36a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,11 +17,11 @@ branches: - /^release\/.*$/ install: - - if [ "$BITS" == "64" ]; then + - 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 + fi; before_script: - if [ "$TRAVIS_OS_NAME" == "osx" ]; then @@ -33,8 +33,6 @@ before_script: addons: apt: - sources: - - sourceline: 'ppa:chris-lea/redis-server' packages: - libc6-dbg - libc6-dev @@ -46,17 +44,13 @@ addons: - libssl-dev - libssl-dev:i386 - valgrind - - redis env: - BITS="32" - BITS="64" script: - - EXTRA_CMAKE_OPTS="-DENABLE_EXAMPLES:BOOL=ON -DENABLE_SSL:BOOL=ON"; - if [ "$BITS" == "64" ]; then - EXTRA_CMAKE_OPTS="$EXTRA_CMAKE_OPTS -DENABLE_SSL_TESTS:BOOL=ON"; - fi; + - 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"; diff --git a/CHANGELOG.md b/CHANGELOG.md index 271f1fcf3c6a..2a2bc314aa78 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,22 @@ +## [1.0.2](https://github.com/redis/hiredis/tree/v1.0.2) - (2021-10-07) + +Announcing Hiredis v1.0.2, which fixes CVE-2021-32765 but returns the SONAME to the correct value of `1.0.0`. + +- [Revert SONAME bump](https://github.com/redis/hiredis/commit/d4e6f109a064690cde64765c654e679fea1d3548) + ([Michael Grunder](https://github.com/michael-grunder)) + +## [1.0.1](https://github.com/redis/hiredis/tree/v1.0.1) - (2021-10-04) + +This release erroneously bumped the SONAME, please use [1.0.2](https://github.com/redis/hiredis/tree/v1.0.2) + +Announcing Hiredis v1.0.1, a security release fixing CVE-2021-32765 + +- Fix for [CVE-2021-32765](https://github.com/redis/hiredis/security/advisories/GHSA-hfm9-39pp-55p2) + [commit](https://github.com/redis/hiredis/commit/76a7b10005c70babee357a7d0f2becf28ec7ed1e) + ([Yossi Gottlieb](https://github.com/yossigo)) + +_Thanks to [Yossi Gottlieb](https://github.com/yossigo) for the security fix and to [Microsoft Security Vulnerability Research](https://www.microsoft.com/en-us/msrc/msvr) for finding the bug._ :sparkling_heart: + ## [1.0.0](https://github.com/redis/hiredis/tree/v1.0.0) - (2020-08-03) Announcing Hiredis v1.0.0, which adds support for RESP3, SSL connections, allocator injection, and better Windows support! :tada: diff --git a/CMakeLists.txt b/CMakeLists.txt index f86c9b70b663..fe6720b28782 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,10 +1,9 @@ CMAKE_MINIMUM_REQUIRED(VERSION 3.4.0) -INCLUDE(GNUInstallDirs) -PROJECT(hiredis) OPTION(ENABLE_SSL "Build hiredis_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_SSL_TESTS "Should we test SSL connections" OFF) +OPTION(ENABLE_ASYNC_TESTS "Should we run all asynchronous API tests" OFF) MACRO(getVersionBit name) SET(VERSION_REGEX "^#define ${name} (.+)$") @@ -20,7 +19,13 @@ getVersionBit(HIREDIS_SONAME) SET(VERSION "${HIREDIS_MAJOR}.${HIREDIS_MINOR}.${HIREDIS_PATCH}") MESSAGE("Detected version: ${VERSION}") -PROJECT(hiredis VERSION "${VERSION}") +PROJECT(hiredis LANGUAGES "C" VERSION "${VERSION}") +INCLUDE(GNUInstallDirs) + +# Hiredis requires C99 +SET(CMAKE_C_STANDARD 99) +SET(CMAKE_POSITION_INDEPENDENT_CODE ON) +SET(CMAKE_DEBUG_POSTFIX d) SET(ENABLE_EXAMPLES OFF CACHE BOOL "Enable building hiredis examples") @@ -41,30 +46,84 @@ IF(WIN32) ENDIF() ADD_LIBRARY(hiredis SHARED ${hiredis_sources}) +ADD_LIBRARY(hiredis_static STATIC ${hiredis_sources}) +ADD_LIBRARY(hiredis::hiredis ALIAS hiredis) +ADD_LIBRARY(hiredis::hiredis_static ALIAS hiredis_static) SET_TARGET_PROPERTIES(hiredis PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS TRUE VERSION "${HIREDIS_SONAME}") +SET_TARGET_PROPERTIES(hiredis_static + PROPERTIES COMPILE_PDB_NAME hiredis_static) +SET_TARGET_PROPERTIES(hiredis_static + PROPERTIES COMPILE_PDB_NAME_DEBUG hiredis_static${CMAKE_DEBUG_POSTFIX}) IF(WIN32 OR MINGW) - TARGET_LINK_LIBRARIES(hiredis PRIVATE ws2_32) + TARGET_LINK_LIBRARIES(hiredis PUBLIC ws2_32 crypt32) + TARGET_LINK_LIBRARIES(hiredis_static PUBLIC ws2_32 crypt32) +ELSEIF(CMAKE_SYSTEM_NAME MATCHES "FreeBSD") + TARGET_LINK_LIBRARIES(hiredis PUBLIC m) + TARGET_LINK_LIBRARIES(hiredis_static PUBLIC m) +ELSEIF(CMAKE_SYSTEM_NAME MATCHES "SunOS") + TARGET_LINK_LIBRARIES(hiredis PUBLIC socket) + TARGET_LINK_LIBRARIES(hiredis_static PUBLIC socket) ENDIF() -TARGET_INCLUDE_DIRECTORIES(hiredis PUBLIC $ $) +TARGET_INCLUDE_DIRECTORIES(hiredis PUBLIC $ $) +TARGET_INCLUDE_DIRECTORIES(hiredis_static PUBLIC $ $) CONFIGURE_FILE(hiredis.pc.in hiredis.pc @ONLY) -INSTALL(TARGETS hiredis +set(CPACK_PACKAGE_VENDOR "Redis") +set(CPACK_PACKAGE_DESCRIPTION "\ +Hiredis is a minimalistic C client library for the Redis 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 Redis 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. + +Hiredis only supports the binary-safe Redis protocol, so you can use it with any Redis \ +version >= 1.2.0. + +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://github.com/redis/hiredis") +set(CPACK_PACKAGE_CONTACT "michael dot grunder at gmail dot com") +set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON) +set(CPACK_RPM_PACKAGE_AUTOREQPROV ON) + +include(CPack) + +INSTALL(TARGETS hiredis hiredis_static EXPORT hiredis-targets RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) +if (MSVC) + INSTALL(FILES $ + DESTINATION ${CMAKE_INSTALL_BINDIR} + CONFIGURATIONS Debug RelWithDebInfo) + INSTALL(FILES $/$.pdb + DESTINATION ${CMAKE_INSTALL_LIBDIR} + CONFIGURATIONS Debug RelWithDebInfo) +endif() + +# For NuGet packages +INSTALL(FILES hiredis.targets + DESTINATION build/native) + INSTALL(FILES hiredis.h read.h sds.h async.h alloc.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/hiredis) - + INSTALL(DIRECTORY adapters DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/hiredis) - + INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/hiredis.pc DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) @@ -95,10 +154,12 @@ IF(ENABLE_SSL) ENDIF() ENDIF() FIND_PACKAGE(OpenSSL REQUIRED) - SET(hiredis_ssl_sources + SET(hiredis_ssl_sources ssl.c) ADD_LIBRARY(hiredis_ssl SHARED ${hiredis_ssl_sources}) + ADD_LIBRARY(hiredis_ssl_static STATIC + ${hiredis_ssl_sources}) IF (APPLE) SET_PROPERTY(TARGET hiredis_ssl PROPERTY LINK_FLAGS "-Wl,-undefined -Wl,dynamic_lookup") @@ -108,23 +169,39 @@ IF(ENABLE_SSL) PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS TRUE VERSION "${HIREDIS_SONAME}") + SET_TARGET_PROPERTIES(hiredis_ssl_static + PROPERTIES COMPILE_PDB_NAME hiredis_ssl_static) + SET_TARGET_PROPERTIES(hiredis_ssl_static + PROPERTIES COMPILE_PDB_NAME_DEBUG hiredis_ssl_static${CMAKE_DEBUG_POSTFIX}) TARGET_INCLUDE_DIRECTORIES(hiredis_ssl PRIVATE "${OPENSSL_INCLUDE_DIR}") + TARGET_INCLUDE_DIRECTORIES(hiredis_ssl_static PRIVATE "${OPENSSL_INCLUDE_DIR}") + TARGET_LINK_LIBRARIES(hiredis_ssl PRIVATE ${OPENSSL_LIBRARIES}) IF (WIN32 OR MINGW) TARGET_LINK_LIBRARIES(hiredis_ssl PRIVATE hiredis) + TARGET_LINK_LIBRARIES(hiredis_ssl_static PUBLIC hiredis_static) ENDIF() CONFIGURE_FILE(hiredis_ssl.pc.in hiredis_ssl.pc @ONLY) - INSTALL(TARGETS hiredis_ssl + INSTALL(TARGETS hiredis_ssl hiredis_ssl_static EXPORT hiredis_ssl-targets RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) + if (MSVC) + INSTALL(FILES $ + DESTINATION ${CMAKE_INSTALL_BINDIR} + CONFIGURATIONS Debug RelWithDebInfo) + INSTALL(FILES $/$.pdb + DESTINATION ${CMAKE_INSTALL_LIBDIR} + CONFIGURATIONS Debug RelWithDebInfo) + endif() + INSTALL(FILES hiredis_ssl.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/hiredis) - + INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/hiredis_ssl.pc DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) @@ -149,11 +226,14 @@ ENDIF() IF(NOT DISABLE_TESTS) ENABLE_TESTING() ADD_EXECUTABLE(hiredis-test test.c) + TARGET_LINK_LIBRARIES(hiredis-test hiredis) IF(ENABLE_SSL_TESTS) ADD_DEFINITIONS(-DHIREDIS_TEST_SSL=1) - TARGET_LINK_LIBRARIES(hiredis-test hiredis hiredis_ssl) - ELSE() - TARGET_LINK_LIBRARIES(hiredis-test hiredis) + TARGET_LINK_LIBRARIES(hiredis-test hiredis_ssl) + ENDIF() + IF(ENABLE_ASYNC_TESTS) + ADD_DEFINITIONS(-DHIREDIS_TEST_ASYNC=1) + TARGET_LINK_LIBRARIES(hiredis-test event) ENDIF() ADD_TEST(NAME hiredis-test COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/test.sh) diff --git a/Makefile b/Makefile index a8d37a2ebbd3..a2ad84c6b18d 100644 --- a/Makefile +++ b/Makefile @@ -4,16 +4,10 @@ # This file is released under the BSD license, see the COPYING file OBJ=alloc.o net.o hiredis.o sds.o async.o read.o sockcompat.o -SSL_OBJ=ssl.o EXAMPLES=hiredis-example hiredis-example-libevent hiredis-example-libev hiredis-example-glib hiredis-example-push -ifeq ($(USE_SSL),1) -EXAMPLES+=hiredis-example-ssl hiredis-example-libevent-ssl -endif TESTS=hiredis-test LIBNAME=libhiredis PKGCONFNAME=hiredis.pc -SSL_LIBNAME=libhiredis_ssl -SSL_PKGCONFNAME=hiredis_ssl.pc HIREDIS_MAJOR=$(shell grep HIREDIS_MAJOR hiredis.h | awk '{print $$3}') HIREDIS_MINOR=$(shell grep HIREDIS_MINOR hiredis.h | awk '{print $$3}') @@ -60,28 +54,66 @@ DYLIB_MAKE_CMD=$(CC) -shared -Wl,-soname,$(DYLIB_MINOR_NAME) STLIBNAME=$(LIBNAME).$(STLIBSUFFIX) STLIB_MAKE_CMD=$(AR) rcs +#################### SSL variables start #################### +SSL_OBJ=ssl.o +SSL_LIBNAME=libhiredis_ssl +SSL_PKGCONFNAME=hiredis_ssl.pc +SSL_INSTALLNAME=install-ssl SSL_DYLIB_MINOR_NAME=$(SSL_LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_SONAME) SSL_DYLIB_MAJOR_NAME=$(SSL_LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_MAJOR) SSL_DYLIBNAME=$(SSL_LIBNAME).$(DYLIBSUFFIX) SSL_STLIBNAME=$(SSL_LIBNAME).$(STLIBSUFFIX) SSL_DYLIB_MAKE_CMD=$(CC) -shared -Wl,-soname,$(SSL_DYLIB_MINOR_NAME) +USE_SSL?=0 +ifeq ($(USE_SSL),1) + # This is required for test.c only + CFLAGS+=-DHIREDIS_TEST_SSL + EXAMPLES+=hiredis-example-ssl hiredis-example-libevent-ssl + SSL_STLIB=$(SSL_STLIBNAME) + SSL_DYLIB=$(SSL_DYLIBNAME) + SSL_PKGCONF=$(SSL_PKGCONFNAME) + SSL_INSTALL=$(SSL_INSTALLNAME) +else + SSL_STLIB= + SSL_DYLIB= + SSL_PKGCONF= + SSL_INSTALL= +endif +##################### SSL variables end ##################### + + # Platform-specific overrides uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not') -USE_SSL?=0 - # This is required for test.c only +ifeq ($(TEST_ASYNC),1) + export CFLAGS+=-DHIREDIS_TEST_ASYNC +endif + ifeq ($(USE_SSL),1) - CFLAGS+=-DHIREDIS_TEST_SSL + ifeq ($(uname_S),Linux) + ifdef OPENSSL_PREFIX + CFLAGS+=-I$(OPENSSL_PREFIX)/include + SSL_LDFLAGS+=-L$(OPENSSL_PREFIX)/lib -lssl -lcrypto + else + SSL_LDFLAGS=-lssl -lcrypto + endif + else + OPENSSL_PREFIX?=/usr/local/opt/openssl + CFLAGS+=-I$(OPENSSL_PREFIX)/include + SSL_LDFLAGS+=-L$(OPENSSL_PREFIX)/lib -lssl -lcrypto + endif endif -ifeq ($(uname_S),Linux) - SSL_LDFLAGS=-lssl -lcrypto +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 - OPENSSL_PREFIX?=/usr/local/opt/openssl - CFLAGS+=-I$(OPENSSL_PREFIX)/include - SSL_LDFLAGS+=-L$(OPENSSL_PREFIX)/lib -lssl -lcrypto + REAL_CFLAGS+=-pedantic endif ifeq ($(uname_S),SunOS) @@ -103,10 +135,13 @@ ifeq ($(uname_S),Darwin) DYLIB_PLUGIN=-Wl,-undefined -Wl,dynamic_lookup endif -all: $(DYLIBNAME) $(STLIBNAME) hiredis-test $(PKGCONFNAME) -ifeq ($(USE_SSL),1) -all: $(SSL_DYLIBNAME) $(SSL_STLIBNAME) $(SSL_PKGCONFNAME) -endif +all: dynamic static hiredis-test pkgconfig + +dynamic: $(DYLIBNAME) $(SSL_DYLIB) + +static: $(STLIBNAME) $(SSL_STLIB) + +pkgconfig: $(PKGCONFNAME) $(SSL_PKGCONF) # Deps (use make dep to generate this) alloc.o: alloc.c fmacros.h alloc.h @@ -117,7 +152,6 @@ net.o: net.c fmacros.h net.h hiredis.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 -ssl.o: ssl.c hiredis.h read.h sds.h alloc.h async.h win32.h async_private.h test.o: test.c fmacros.h hiredis.h read.h sds.h alloc.h net.h sockcompat.h win32.h $(DYLIBNAME): $(OBJ) @@ -126,18 +160,15 @@ $(DYLIBNAME): $(OBJ) $(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) -dynamic: $(DYLIBNAME) -static: $(STLIBNAME) -ifeq ($(USE_SSL),1) -dynamic: $(SSL_DYLIBNAME) -static: $(SSL_STLIBNAME) -endif +$(SSL_OBJ): ssl.c hiredis.h read.h sds.h alloc.h async.h win32.h async_private.h +#################### SSL building rules end #################### # Binaries: hiredis-example-libevent: examples/example-libevent.c adapters/libevent.h $(STLIBNAME) @@ -161,7 +192,6 @@ hiredis-example-macosx: examples/example-macosx.c adapters/macosx.h $(STLIBNAME) hiredis-example-ssl: examples/example-ssl.c $(STLIBNAME) $(SSL_STLIBNAME) $(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< $(STLIBNAME) $(SSL_STLIBNAME) $(REAL_LDFLAGS) $(SSL_LDFLAGS) - ifndef AE_DIR hiredis-example-ae: @echo "Please specify AE_DIR (e.g. /src)" @@ -172,10 +202,11 @@ hiredis-example-ae: examples/example-ae.c adapters/ae.h $(STLIBNAME) endif ifndef LIBUV_DIR -hiredis-example-libuv: - @echo "Please specify LIBUV_DIR (e.g. ../libuv/)" - @false +# dynamic link libuv.so +hiredis-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 hiredis-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 @@ -201,10 +232,13 @@ hiredis-example-push: examples/example-push.c $(STLIBNAME) examples: $(EXAMPLES) -TEST_LIBS = $(STLIBNAME) +TEST_LIBS = $(STLIBNAME) $(SSL_STLIB) +TEST_LDFLAGS = $(SSL_LDFLAGS) ifeq ($(USE_SSL),1) - TEST_LIBS += $(SSL_STLIBNAME) - TEST_LDFLAGS = $(SSL_LDFLAGS) -lssl -lcrypto -lpthread + TEST_LDFLAGS += -pthread +endif +ifeq ($(TEST_ASYNC),1) + TEST_LDFLAGS += -levent endif hiredis-test: test.o $(TEST_LIBS) @@ -220,7 +254,7 @@ check: hiredis-test TEST_SSL=$(USE_SSL) ./test.sh .c.o: - $(CC) -std=c99 -pedantic -c $(REAL_CFLAGS) $< + $(CC) -std=c99 -c $(REAL_CFLAGS) $< clean: rm -rf $(DYLIBNAME) $(STLIBNAME) $(SSL_DYLIBNAME) $(SSL_STLIBNAME) $(TESTS) $(PKGCONFNAME) examples/hiredis-example* *.o *.gcda *.gcno *.gcov @@ -257,7 +291,7 @@ $(SSL_PKGCONFNAME): hiredis_ssl.h @echo Libs: -L\$${libdir} -lhiredis_ssl >> $@ @echo Libs.private: -lssl -lcrypto >> $@ -install: $(DYLIBNAME) $(STLIBNAME) $(PKGCONFNAME) +install: $(DYLIBNAME) $(STLIBNAME) $(PKGCONFNAME) $(SSL_INSTALL) mkdir -p $(INSTALL_INCLUDE_PATH) $(INSTALL_INCLUDE_PATH)/adapters $(INSTALL_LIBRARY_PATH) $(INSTALL) hiredis.h async.h read.h sds.h alloc.h $(INSTALL_INCLUDE_PATH) $(INSTALL) adapters/*.h $(INSTALL_INCLUDE_PATH)/adapters @@ -267,9 +301,6 @@ install: $(DYLIBNAME) $(STLIBNAME) $(PKGCONFNAME) mkdir -p $(INSTALL_PKGCONF_PATH) $(INSTALL) $(PKGCONFNAME) $(INSTALL_PKGCONF_PATH) -ifeq ($(USE_SSL),1) -install: install-ssl - install-ssl: $(SSL_DYLIBNAME) $(SSL_STLIBNAME) $(SSL_PKGCONFNAME) mkdir -p $(INSTALL_INCLUDE_PATH) $(INSTALL_LIBRARY_PATH) $(INSTALL) hiredis_ssl.h $(INSTALL_INCLUDE_PATH) @@ -278,7 +309,6 @@ install-ssl: $(SSL_DYLIBNAME) $(SSL_STLIBNAME) $(SSL_PKGCONFNAME) $(INSTALL) $(SSL_STLIBNAME) $(INSTALL_LIBRARY_PATH) mkdir -p $(INSTALL_PKGCONF_PATH) $(INSTALL) $(SSL_PKGCONFNAME) $(INSTALL_PKGCONF_PATH) -endif 32bit: @echo "" @@ -294,12 +324,12 @@ gprof: $(MAKE) CFLAGS="-pg" LDFLAGS="-pg" gcov: - $(MAKE) CFLAGS="-fprofile-arcs -ftest-coverage" LDFLAGS="-fprofile-arcs" + $(MAKE) CFLAGS+="-fprofile-arcs -ftest-coverage" LDFLAGS="-fprofile-arcs" coverage: gcov make check mkdir -p tmp/lcov - lcov -d . -c -o tmp/lcov/hiredis.info + lcov -d . -c --exclude '/usr*' -o tmp/lcov/hiredis.info genhtml --legend -o tmp/lcov/report tmp/lcov/hiredis.info noopt: diff --git a/README.md b/README.md index 3a22553eaa15..ed66220c7d69 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,11 @@ -[![Build Status](https://travis-ci.org/redis/hiredis.png)](https://travis-ci.org/redis/hiredis) + +[![Build Status](https://github.com/redis/hiredis/actions/workflows/build.yml/badge.svg)](https://github.com/redis/hiredis/actions/workflows/build.yml) **This Readme reflects the latest changed in the master branch. See [v1.0.0](https://github.com/redis/hiredis/tree/v1.0.0) for the Readme and documentation for the latest release ([API/ABI history](https://abi-laboratory.pro/?view=timeline&l=hiredis)).** # HIREDIS -Hiredis is a minimalistic C client library for the [Redis](http://redis.io/) database. +Hiredis is a minimalistic C client library for the [Redis](https://redis.io/) 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 @@ -22,6 +23,12 @@ Redis version >= 1.2.0. The library comes with multiple APIs. There is the *synchronous API*, the *asynchronous API* and the *reply parsing API*. +## Upgrading to `1.0.2` + +NOTE: v1.0.1 erroneously bumped SONAME, which is why it is skipped here. + +Version 1.0.2 is simply 1.0.0 with a fix for [CVE-2021-32765](https://github.com/redis/hiredis/security/advisories/GHSA-hfm9-39pp-55p2). They are otherwise identical. + ## Upgrading to `1.0.0` Version 1.0.0 marks the first stable release of Hiredis. @@ -169,7 +176,7 @@ Hiredis also supports every new `RESP3` data type which are as follows. For mor * **`REDIS_REPLY_MAP`**: * An array with the added invariant that there will always be an even number of elements. - The MAP is functionally equivelant to `REDIS_REPLY_ARRAY` except for the previously mentioned invariant. + The MAP is functionally equivalent to `REDIS_REPLY_ARRAY` except for the previously mentioned invariant. * **`REDIS_REPLY_SET`**: * An array response where each entry is unique. @@ -189,7 +196,7 @@ Hiredis also supports every new `RESP3` data type which are as follows. For mor * **`REDIS_REPLY_VERB`**: * A verbatim string, intended to be presented to the user without modification. - The string payload is stored in the `str` memeber, and type data is stored in the `vtype` member (e.g. `txt` for raw text or `md` for markdown). + The string payload is stored in the `str` member, and type data is stored in the `vtype` member (e.g. `txt` for raw text or `md` for markdown). Replies should be freed using the `freeReplyObject()` function. Note that this function will take care of freeing sub-reply objects @@ -261,9 +268,9 @@ a single call to `read(2)`): redisReply *reply; redisAppendCommand(context,"SET foo bar"); redisAppendCommand(context,"GET foo"); -redisGetReply(context,(void *)&reply); // reply for SET +redisGetReply(context,(void**)&reply); // reply for SET freeReplyObject(reply); -redisGetReply(context,(void *)&reply); // reply for GET +redisGetReply(context,(void**)&reply); // reply for GET freeReplyObject(reply); ``` This API can also be used to implement a blocking subscriber: @@ -517,7 +524,7 @@ initialize OpenSSL and create a context. You can do that in two ways: /* An Hiredis SSL context. It holds SSL configuration and can be reused across * many contexts. */ -redisSSLContext *ssl; +redisSSLContext *ssl_context; /* An error variable to indicate what went wrong, if the context fails to * initialize. @@ -532,17 +539,23 @@ redisSSLContextError ssl_error; redisInitOpenSSL(); /* Create SSL context */ -ssl = redisCreateSSLContext( +ssl_context = redisCreateSSLContext( "cacertbundle.crt", /* File name of trusted CA/ca bundle file, optional */ "/path/to/certs", /* Path of trusted certificates, optional */ "client_cert.pem", /* File name of client certificate file, optional */ "client_key.pem", /* File name of client private key, optional */ "redis.mydomain.com", /* Server name to request (SNI), optional */ - &ssl_error - ) != REDIS_OK) { - printf("SSL error: %s\n", redisSSLContextGetError(ssl_error); - /* Abort... */ - } + &ssl_error); + +if(ssl_context == NULL || ssl_error != 0) { + /* Handle error and abort... */ + /* e.g. + printf("SSL error: %s\n", + (ssl_error != 0) ? + redisSSLContextGetError(ssl_error) : "Unknown error"); + // Abort + */ +} /* Create Redis context and establish connection */ c = redisConnect("localhost", 6443); @@ -551,7 +564,7 @@ if (c == NULL || c->err) { } /* Negotiate SSL/TLS */ -if (redisInitiateSSLWithContext(c, ssl) != REDIS_OK) { +if (redisInitiateSSLWithContext(c, ssl_context) != REDIS_OK) { /* Handle error, in c->err / c->errstr */ } ``` diff --git a/adapters/libev.h b/adapters/libev.h index e1e7bbd99c94..c59d3da770fe 100644 --- a/adapters/libev.h +++ b/adapters/libev.h @@ -46,7 +46,7 @@ typedef struct redisLibevEvents { static void redisLibevReadEvent(EV_P_ ev_io *watcher, int revents) { #if EV_MULTIPLICITY - ((void)loop); + ((void)EV_A); #endif ((void)revents); @@ -56,7 +56,7 @@ static void redisLibevReadEvent(EV_P_ ev_io *watcher, int revents) { static void redisLibevWriteEvent(EV_P_ ev_io *watcher, int revents) { #if EV_MULTIPLICITY - ((void)loop); + ((void)EV_A); #endif ((void)revents); @@ -66,8 +66,9 @@ static void redisLibevWriteEvent(EV_P_ ev_io *watcher, int revents) { static void redisLibevAddRead(void *privdata) { redisLibevEvents *e = (redisLibevEvents*)privdata; +#if EV_MULTIPLICITY struct ev_loop *loop = e->loop; - ((void)loop); +#endif if (!e->reading) { e->reading = 1; ev_io_start(EV_A_ &e->rev); @@ -76,8 +77,9 @@ static void redisLibevAddRead(void *privdata) { static void redisLibevDelRead(void *privdata) { redisLibevEvents *e = (redisLibevEvents*)privdata; +#if EV_MULTIPLICITY struct ev_loop *loop = e->loop; - ((void)loop); +#endif if (e->reading) { e->reading = 0; ev_io_stop(EV_A_ &e->rev); @@ -86,8 +88,9 @@ static void redisLibevDelRead(void *privdata) { static void redisLibevAddWrite(void *privdata) { redisLibevEvents *e = (redisLibevEvents*)privdata; +#if EV_MULTIPLICITY struct ev_loop *loop = e->loop; - ((void)loop); +#endif if (!e->writing) { e->writing = 1; ev_io_start(EV_A_ &e->wev); @@ -96,8 +99,9 @@ static void redisLibevAddWrite(void *privdata) { static void redisLibevDelWrite(void *privdata) { redisLibevEvents *e = (redisLibevEvents*)privdata; +#if EV_MULTIPLICITY struct ev_loop *loop = e->loop; - ((void)loop); +#endif if (e->writing) { e->writing = 0; ev_io_stop(EV_A_ &e->wev); @@ -106,8 +110,9 @@ static void redisLibevDelWrite(void *privdata) { static void redisLibevStopTimer(void *privdata) { redisLibevEvents *e = (redisLibevEvents*)privdata; +#if EV_MULTIPLICITY struct ev_loop *loop = e->loop; - ((void)loop); +#endif ev_timer_stop(EV_A_ &e->timer); } @@ -120,6 +125,9 @@ static void redisLibevCleanup(void *privdata) { } static void redisLibevTimeout(EV_P_ ev_timer *timer, int revents) { +#if EV_MULTIPLICITY + ((void)EV_A); +#endif ((void)revents); redisLibevEvents *e = (redisLibevEvents*)timer->data; redisAsyncHandleTimeout(e->context); @@ -127,8 +135,9 @@ static void redisLibevTimeout(EV_P_ ev_timer *timer, int revents) { static void redisLibevSetTimeout(void *privdata, struct timeval tv) { redisLibevEvents *e = (redisLibevEvents*)privdata; +#if EV_MULTIPLICITY struct ev_loop *loop = e->loop; - ((void)loop); +#endif if (!ev_is_active(&e->timer)) { ev_init(&e->timer, redisLibevTimeout); @@ -154,7 +163,7 @@ static int redisLibevAttach(EV_P_ redisAsyncContext *ac) { e->context = ac; #if EV_MULTIPLICITY - e->loop = loop; + e->loop = EV_A; #else e->loop = NULL; #endif diff --git a/adapters/libevent.h b/adapters/libevent.h index 9150979bc6c5..73bb8ed75ada 100644 --- a/adapters/libevent.h +++ b/adapters/libevent.h @@ -50,7 +50,7 @@ static void redisLibeventDestroy(redisLibeventEvents *e) { hi_free(e); } -static void redisLibeventHandler(int fd, short event, void *arg) { +static void redisLibeventHandler(evutil_socket_t fd, short event, void *arg) { ((void)fd); redisLibeventEvents *e = (redisLibeventEvents*)arg; e->state |= REDIS_LIBEVENT_ENTERED; diff --git a/adapters/libuv.h b/adapters/libuv.h index c120b1b396bc..df0a845780a6 100644 --- a/adapters/libuv.h +++ b/adapters/libuv.h @@ -7,111 +7,157 @@ #include typedef struct redisLibuvEvents { - redisAsyncContext* context; - uv_poll_t handle; - int events; + redisAsyncContext* context; + uv_poll_t handle; + uv_timer_t timer; + int events; } redisLibuvEvents; static void redisLibuvPoll(uv_poll_t* handle, int status, int events) { - redisLibuvEvents* p = (redisLibuvEvents*)handle->data; - int ev = (status ? p->events : events); - - if (p->context != NULL && (ev & UV_READABLE)) { - redisAsyncHandleRead(p->context); - } - if (p->context != NULL && (ev & UV_WRITABLE)) { - redisAsyncHandleWrite(p->context); - } + redisLibuvEvents* p = (redisLibuvEvents*)handle->data; + int ev = (status ? p->events : events); + + if (p->context != NULL && (ev & UV_READABLE)) { + redisAsyncHandleRead(p->context); + } + if (p->context != NULL && (ev & UV_WRITABLE)) { + redisAsyncHandleWrite(p->context); + } } static void redisLibuvAddRead(void *privdata) { - redisLibuvEvents* p = (redisLibuvEvents*)privdata; + redisLibuvEvents* p = (redisLibuvEvents*)privdata; - p->events |= UV_READABLE; + p->events |= UV_READABLE; - uv_poll_start(&p->handle, p->events, redisLibuvPoll); + uv_poll_start(&p->handle, p->events, redisLibuvPoll); } static void redisLibuvDelRead(void *privdata) { - redisLibuvEvents* p = (redisLibuvEvents*)privdata; + redisLibuvEvents* p = (redisLibuvEvents*)privdata; - p->events &= ~UV_READABLE; + p->events &= ~UV_READABLE; - if (p->events) { - uv_poll_start(&p->handle, p->events, redisLibuvPoll); - } else { - uv_poll_stop(&p->handle); - } + if (p->events) { + uv_poll_start(&p->handle, p->events, redisLibuvPoll); + } else { + uv_poll_stop(&p->handle); + } } static void redisLibuvAddWrite(void *privdata) { - redisLibuvEvents* p = (redisLibuvEvents*)privdata; + redisLibuvEvents* p = (redisLibuvEvents*)privdata; - p->events |= UV_WRITABLE; + p->events |= UV_WRITABLE; - uv_poll_start(&p->handle, p->events, redisLibuvPoll); + uv_poll_start(&p->handle, p->events, redisLibuvPoll); } static void redisLibuvDelWrite(void *privdata) { - redisLibuvEvents* p = (redisLibuvEvents*)privdata; + redisLibuvEvents* p = (redisLibuvEvents*)privdata; - p->events &= ~UV_WRITABLE; + p->events &= ~UV_WRITABLE; - if (p->events) { - uv_poll_start(&p->handle, p->events, redisLibuvPoll); - } else { - uv_poll_stop(&p->handle); - } + if (p->events) { + uv_poll_start(&p->handle, p->events, redisLibuvPoll); + } else { + uv_poll_stop(&p->handle); + } } +static void on_timer_close(uv_handle_t *handle) { + redisLibuvEvents* p = (redisLibuvEvents*)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_close(uv_handle_t* handle) { - redisLibuvEvents* p = (redisLibuvEvents*)handle->data; +static void on_handle_close(uv_handle_t *handle) { + redisLibuvEvents* p = (redisLibuvEvents*)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` +} - hi_free(p); +// 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 redisLibuvTimeout(uv_timer_t *timer, int status) { + (void)status; // unused +#else +static void redisLibuvTimeout(uv_timer_t *timer) { +#endif + redisLibuvEvents *e = (redisLibuvEvents*)timer->data; + redisAsyncHandleTimeout(e->context); } +static void redisLibuvSetTimeout(void *privdata, struct timeval tv) { + redisLibuvEvents* p = (redisLibuvEvents*)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, redisLibuvTimeout, millsec, 0); +} static void redisLibuvCleanup(void *privdata) { - redisLibuvEvents* p = (redisLibuvEvents*)privdata; + redisLibuvEvents* p = (redisLibuvEvents*)privdata; - p->context = NULL; // indicate that context might no longer exist - uv_close((uv_handle_t*)&p->handle, on_close); + 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 redisLibuvAttach(redisAsyncContext* ac, uv_loop_t* loop) { - redisContext *c = &(ac->c); + redisContext *c = &(ac->c); - if (ac->ev.data != NULL) { - return REDIS_ERR; - } + if (ac->ev.data != NULL) { + return REDIS_ERR; + } - ac->ev.addRead = redisLibuvAddRead; - ac->ev.delRead = redisLibuvDelRead; - ac->ev.addWrite = redisLibuvAddWrite; - ac->ev.delWrite = redisLibuvDelWrite; - ac->ev.cleanup = redisLibuvCleanup; + ac->ev.addRead = redisLibuvAddRead; + ac->ev.delRead = redisLibuvDelRead; + ac->ev.addWrite = redisLibuvAddWrite; + ac->ev.delWrite = redisLibuvDelWrite; + ac->ev.cleanup = redisLibuvCleanup; + ac->ev.scheduleTimer = redisLibuvSetTimeout; - redisLibuvEvents* p = (redisLibuvEvents*)hi_malloc(sizeof(*p)); - if (p == NULL) - return REDIS_ERR; + redisLibuvEvents* p = (redisLibuvEvents*)hi_malloc(sizeof(*p)); + if (p == NULL) + return REDIS_ERR; - memset(p, 0, sizeof(*p)); + memset(p, 0, sizeof(*p)); - if (uv_poll_init_socket(loop, &p->handle, c->fd) != 0) { - return REDIS_ERR; - } + if (uv_poll_init_socket(loop, &p->handle, c->fd) != 0) { + return REDIS_ERR; + } - ac->ev.data = p; - p->handle.data = p; - p->context = ac; + ac->ev.data = p; + p->handle.data = p; + p->context = ac; - return REDIS_OK; + return REDIS_OK; } #endif diff --git a/alloc.c b/alloc.c index 7fb6b35e778d..0902286c7545 100644 --- a/alloc.c +++ b/alloc.c @@ -68,6 +68,10 @@ void *hi_malloc(size_t 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 hiredisAllocFns.callocFn(nmemb, size); } diff --git a/alloc.h b/alloc.h index 34a05f49f316..771f9fee53fe 100644 --- a/alloc.h +++ b/alloc.h @@ -32,6 +32,7 @@ #define HIREDIS_ALLOC_H #include /* for size_t */ +#include #ifdef __cplusplus extern "C" { @@ -59,6 +60,10 @@ static inline void *hi_malloc(size_t 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 hiredisAllocFns.callocFn(nmemb, size); } diff --git a/async.c b/async.c index 64ab601c956e..65551142b958 100644 --- a/async.c +++ b/async.c @@ -47,6 +47,11 @@ #include "async_private.h" +#ifdef NDEBUG +#undef assert +#define assert(e) (void)(e) +#endif + /* Forward declarations of hiredis.c functions */ int __redisAppendCommand(redisContext *c, const char *cmd, size_t len); void __redisSetError(redisContext *c, int type, const char *str); @@ -54,7 +59,7 @@ void __redisSetError(redisContext *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, - hi_sdslen((const hisds)key)); + sdslen((const sds)key)); } static void *callbackValDup(void *privdata, const void *src) { @@ -73,15 +78,15 @@ static int callbackKeyCompare(void *privdata, const void *key1, const void *key2 int l1, l2; ((void) privdata); - l1 = hi_sdslen((const hisds)key1); - l2 = hi_sdslen((const hisds)key2); + 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); - hi_sdsfree((hisds)key); + sdsfree((sds)key); } static void callbackValDestructor(void *privdata, void *val) { @@ -139,8 +144,8 @@ static redisAsyncContext *redisAsyncInitialize(redisContext *c) { ac->replies.head = NULL; ac->replies.tail = NULL; - ac->sub.invalid.head = NULL; - ac->sub.invalid.tail = NULL; + ac->sub.replies.head = NULL; + ac->sub.replies.tail = NULL; ac->sub.channels = channels; ac->sub.patterns = patterns; @@ -301,36 +306,28 @@ static void __redisRunPushCallback(redisAsyncContext *ac, redisReply *reply) { static void __redisAsyncFree(redisAsyncContext *ac) { redisContext *c = &(ac->c); redisCallback cb; - dictIterator *it; + dictIterator it; dictEntry *de; /* Execute pending callbacks with NULL reply. */ while (__redisShiftCallback(&ac->replies,&cb) == REDIS_OK) __redisRunCallback(ac,&cb,NULL); - - /* Execute callbacks for invalid commands */ - while (__redisShiftCallback(&ac->sub.invalid,&cb) == REDIS_OK) + while (__redisShiftCallback(&ac->sub.replies,&cb) == REDIS_OK) __redisRunCallback(ac,&cb,NULL); /* Run subscription callbacks with NULL reply */ if (ac->sub.channels) { - it = dictGetIterator(ac->sub.channels); - if (it != NULL) { - while ((de = dictNext(it)) != NULL) - __redisRunCallback(ac,dictGetEntryVal(de),NULL); - dictReleaseIterator(it); - } + dictInitIterator(&it,ac->sub.channels); + while ((de = dictNext(&it)) != NULL) + __redisRunCallback(ac,dictGetEntryVal(de),NULL); dictRelease(ac->sub.channels); } if (ac->sub.patterns) { - it = dictGetIterator(ac->sub.patterns); - if (it != NULL) { - while ((de = dictNext(it)) != NULL) - __redisRunCallback(ac,dictGetEntryVal(de),NULL); - dictReleaseIterator(it); - } + dictInitIterator(&it,ac->sub.patterns); + while ((de = dictNext(&it)) != NULL) + __redisRunCallback(ac,dictGetEntryVal(de),NULL); dictRelease(ac->sub.patterns); } @@ -418,12 +415,13 @@ static int __redisGetSubscribeCallback(redisAsyncContext *ac, redisReply *reply, dictEntry *de; int pvariant; char *stype; - hisds sname; + sds sname; - /* Custom reply functions are not supported for pub/sub. This will fail - * very hard when they are used... */ - if (reply->type == REDIS_REPLY_ARRAY || reply->type == REDIS_REPLY_PUSH) { - assert(reply->elements >= 2); + /* Match reply with the expected format of a pushed message. + * The type and number of elements (3 to 4) are specified at: + * https://redis.io/topics/pubsub#format-of-pushed-messages */ + if ((reply->type == REDIS_REPLY_ARRAY && !(c->flags & REDIS_SUPPORTS_PUSH) && reply->elements >= 3) || + reply->type == REDIS_REPLY_PUSH) { assert(reply->element[0]->type == REDIS_REPLY_STRING); stype = reply->element[0]->str; pvariant = (tolower(stype[0]) == 'p') ? 1 : 0; @@ -435,7 +433,7 @@ static int __redisGetSubscribeCallback(redisAsyncContext *ac, redisReply *reply, /* Locate the right callback */ assert(reply->element[1]->type == REDIS_REPLY_STRING); - sname = hi_sdsnewlen(reply->element[1]->str,reply->element[1]->len); + sname = sdsnewlen(reply->element[1]->str,reply->element[1]->len); if (sname == NULL) goto oom; @@ -462,14 +460,21 @@ static int __redisGetSubscribeCallback(redisAsyncContext *ac, redisReply *reply, /* Unset subscribed flag only when no pipelined pending subscribe. */ if (reply->element[2]->integer == 0 && dictSize(ac->sub.channels) == 0 - && dictSize(ac->sub.patterns) == 0) + && dictSize(ac->sub.patterns) == 0) { c->flags &= ~REDIS_SUBSCRIBED; + + /* Move ongoing regular command callbacks. */ + redisCallback cb; + while (__redisShiftCallback(&ac->sub.replies,&cb) == REDIS_OK) { + __redisPushCallback(&ac->replies,&cb); + } + } } } - hi_sdsfree(sname); + sdsfree(sname); } else { - /* Shift callback for invalid commands. */ - __redisShiftCallback(&ac->sub.invalid,dstcb); + /* Shift callback for pending command in subscribed context. */ + __redisShiftCallback(&ac->sub.replies,dstcb); } return REDIS_OK; oom: @@ -497,13 +502,12 @@ static int redisIsSubscribeReply(redisReply *reply) { len = reply->element[0]->len - off; return !strncasecmp(str, "subscribe", len) || - !strncasecmp(str, "message", len); - + !strncasecmp(str, "message", len) || + !strncasecmp(str, "unsubscribe", len); } void redisProcessCallbacks(redisAsyncContext *ac) { redisContext *c = &(ac->c); - redisCallback cb = {NULL, NULL, 0, NULL}; void *reply = NULL; int status; @@ -511,22 +515,19 @@ void redisProcessCallbacks(redisAsyncContext *ac) { 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 & REDIS_DISCONNECTING && hi_sdslen(c->obuf) == 0 + if (c->flags & REDIS_DISCONNECTING && sdslen(c->obuf) == 0 && ac->replies.head == NULL) { __redisAsyncDisconnect(ac); return; } - - /* If monitor mode, repush callback */ - if(c->flags & REDIS_MONITORING) { - __redisPushCallback(&ac->replies,&cb); - } - /* 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 (redisIsPushReply(reply)) c->flags |= REDIS_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 @@ -539,6 +540,7 @@ void redisProcessCallbacks(redisAsyncContext *ac) { /* Even if the context is subscribed, pending regular * callbacks will get a reply before pub/sub messages arrive. */ + redisCallback cb = {NULL, NULL, 0, NULL}; if (__redisShiftCallback(&ac->replies,&cb) != REDIS_OK) { /* * A spontaneous reply in a not-subscribed context can be the error @@ -562,15 +564,17 @@ void redisProcessCallbacks(redisAsyncContext *ac) { __redisAsyncDisconnect(ac); return; } - /* No more regular callbacks and no errors, the context *must* be subscribed or monitoring. */ - assert((c->flags & REDIS_SUBSCRIBED || c->flags & REDIS_MONITORING)); - if(c->flags & REDIS_SUBSCRIBED) + /* No more regular callbacks and no errors, the context *must* be subscribed. */ + assert(c->flags & REDIS_SUBSCRIBED); + if (c->flags & REDIS_SUBSCRIBED) __redisGetSubscribeCallback(ac,reply,&cb); } if (cb.fn != NULL) { __redisRunCallback(ac,&cb,reply); - c->reader->fn->freeObject(reply); + if (!(c->flags & REDIS_NO_AUTO_FREE_REPLIES)){ + c->reader->fn->freeObject(reply); + } /* Proceed with free'ing when redisAsyncFree() was called. */ if (c->flags & REDIS_FREEING) { @@ -584,6 +588,11 @@ void redisProcessCallbacks(redisAsyncContext *ac) { * 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 & REDIS_MONITORING) { + __redisPushCallback(&ac->replies,&cb); + } } /* Disconnect when there was an error reading the reply */ @@ -605,7 +614,8 @@ static int __redisAsyncHandleConnect(redisAsyncContext *ac) { if (redisCheckConnectDone(c, &completed) == REDIS_ERR) { /* Error! */ - redisCheckSocketError(c); + if (redisCheckSocketError(c) == REDIS_ERR) + __redisAsyncCopyError(ac); __redisAsyncHandleConnectFailure(ac); return REDIS_ERR; } else if (completed == 1) { @@ -691,13 +701,22 @@ void redisAsyncHandleTimeout(redisAsyncContext *ac) { redisContext *c = &(ac->c); redisCallback cb; - if ((c->flags & REDIS_CONNECTED) && ac->replies.head == NULL) { - /* Nothing to do - just an idle timeout */ - return; + if ((c->flags & REDIS_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) { __redisSetError(c, REDIS_ERR_TIMEOUT, "Timeout"); + __redisAsyncCopyError(ac); } if (!(c->flags & REDIS_CONNECTED) && ac->onConnect) { @@ -744,7 +763,7 @@ static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void const char *cstr, *astr; size_t clen, alen; const char *p; - hisds sname; + sds sname; int ret; /* Don't accept new commands when the connection is about to be closed. */ @@ -768,7 +787,7 @@ static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void /* Add every channel/pattern to the list of subscription callbacks. */ while ((p = nextArgument(p,&astr,&alen)) != NULL) { - sname = hi_sdsnewlen(astr,alen); + sname = sdsnewlen(astr,alen); if (sname == NULL) goto oom; @@ -786,7 +805,7 @@ static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void ret = dictReplace(cbdict,sname,&cb); - if (ret == 0) hi_sdsfree(sname); + 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 @@ -796,17 +815,19 @@ static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void /* (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 |= REDIS_MONITORING; - __redisPushCallback(&ac->replies,&cb); + } else if (strncasecmp(cstr,"monitor\r\n",9) == 0) { + /* Set monitor flag and push callback */ + c->flags |= REDIS_MONITORING; + if (__redisPushCallback(&ac->replies,&cb) != REDIS_OK) + goto oom; } else { - if (c->flags & REDIS_SUBSCRIBED) - /* This will likely result in an error reply, but it needs to be - * received and passed to the callback. */ - __redisPushCallback(&ac->sub.invalid,&cb); - else - __redisPushCallback(&ac->replies,&cb); + if (c->flags & REDIS_SUBSCRIBED) { + if (__redisPushCallback(&ac->sub.replies,&cb) != REDIS_OK) + goto oom; + } else { + if (__redisPushCallback(&ac->replies,&cb) != REDIS_OK) + goto oom; + } } __redisAppendCommand(c,cmd,len); @@ -817,6 +838,7 @@ static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void return REDIS_OK; oom: __redisSetError(&(ac->c), REDIS_ERR_OOM, "Out of memory"); + __redisAsyncCopyError(ac); return REDIS_ERR; } @@ -845,14 +867,14 @@ int redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata } int redisAsyncCommandArgv(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen) { - hisds cmd; - int len; + sds cmd; + long long len; int status; len = redisFormatSdsCommandArgv(&cmd,argc,argv,argvlen); if (len < 0) return REDIS_ERR; status = __redisAsyncCommand(ac,fn,privdata,cmd,len); - hi_sdsfree(cmd); + sdsfree(cmd); return status; } diff --git a/async.h b/async.h index b1d2cb263f99..4c65203c14a7 100644 --- a/async.h +++ b/async.h @@ -102,7 +102,7 @@ typedef struct redisAsyncContext { /* Subscription callbacks */ struct { - redisCallbackList invalid; + redisCallbackList replies; struct dict *channels; struct dict *patterns; } sub; diff --git a/dict.c b/dict.c index 34a33ead991f..ad571818e27d 100644 --- a/dict.c +++ b/dict.c @@ -267,16 +267,11 @@ static dictEntry *dictFind(dict *ht, const void *key) { return NULL; } -static dictIterator *dictGetIterator(dict *ht) { - dictIterator *iter = hi_malloc(sizeof(*iter)); - if (iter == NULL) - return NULL; - +static void dictInitIterator(dictIterator *iter, dict *ht) { iter->ht = ht; iter->index = -1; iter->entry = NULL; iter->nextEntry = NULL; - return iter; } static dictEntry *dictNext(dictIterator *iter) { @@ -299,10 +294,6 @@ static dictEntry *dictNext(dictIterator *iter) { return NULL; } -static void dictReleaseIterator(dictIterator *iter) { - hi_free(iter); -} - /* ------------------------- private functions ------------------------------ */ /* Expand the hash table if needed */ diff --git a/dict.h b/dict.h index 95fcd280e2cf..6ad0acd8d22e 100644 --- a/dict.h +++ b/dict.h @@ -119,8 +119,7 @@ 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 dictIterator *dictGetIterator(dict *ht); +static void dictInitIterator(dictIterator *iter, dict *ht); static dictEntry *dictNext(dictIterator *iter); -static void dictReleaseIterator(dictIterator *iter); #endif /* __DICT_H */ diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 1d5bc56e0b05..49cd8d4402cd 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -21,7 +21,7 @@ ENDIF() FIND_PATH(LIBEVENT event.h) if (LIBEVENT) - ADD_EXECUTABLE(example-libevent example-libevent) + ADD_EXECUTABLE(example-libevent example-libevent.c) TARGET_LINK_LIBRARIES(example-libevent hiredis event) ENDIF() diff --git a/examples/example-libuv.c b/examples/example-libuv.c index cbde452b9684..53fd04a8e437 100644 --- a/examples/example-libuv.c +++ b/examples/example-libuv.c @@ -7,18 +7,33 @@ #include #include +void debugCallback(redisAsyncContext *c, void *r, void *privdata) { + (void)privdata; //unused + redisReply *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)*/ + redisAsyncDisconnect(c); +} + void getCallback(redisAsyncContext *c, void *r, void *privdata) { redisReply *reply = r; - if (reply == NULL) return; - printf("argv[%s]: %s\n", (char*)privdata, reply->str); + 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); - /* Disconnect after receiving the reply to GET */ - redisAsyncDisconnect(c); + /* start another request that demonstrate timeout */ + redisAsyncCommand(c, debugCallback, NULL, "DEBUG SLEEP %f", 1.5); } void connectCallback(const redisAsyncContext *c, int status) { if (status != REDIS_OK) { - printf("Error: %s\n", c->errstr); + printf("connect error: %s\n", c->errstr); return; } printf("Connected...\n"); @@ -26,7 +41,7 @@ void connectCallback(const redisAsyncContext *c, int status) { void disconnectCallback(const redisAsyncContext *c, int status) { if (status != REDIS_OK) { - printf("Error: %s\n", c->errstr); + printf("disconnect because of error: %s\n", c->errstr); return; } printf("Disconnected...\n"); @@ -49,8 +64,18 @@ int main (int argc, char **argv) { redisLibuvAttach(c,loop); redisAsyncSetConnectCallback(c,connectCallback); redisAsyncSetDisconnectCallback(c,disconnectCallback); + redisAsyncSetTimeout(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`. + */ + redisAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1])); redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key"); + uv_run(loop, UV_RUN_DEFAULT); return 0; } diff --git a/examples/example-push.c b/examples/example-push.c index 2d4ab4dc0696..6bc12055e651 100644 --- a/examples/example-push.c +++ b/examples/example-push.c @@ -31,7 +31,6 @@ #include #include #include -#include #define KEY_COUNT 5 diff --git a/examples/example-ssl.c b/examples/example-ssl.c index c754177cf3b7..b8ca44281b19 100644 --- a/examples/example-ssl.c +++ b/examples/example-ssl.c @@ -4,7 +4,10 @@ #include #include -#include + +#ifdef _MSC_VER +#include /* For struct timeval */ +#endif int main(int argc, char **argv) { unsigned int j; diff --git a/examples/example.c b/examples/example.c index 15dacbd18dff..f1b8b4a857be 100644 --- a/examples/example.c +++ b/examples/example.c @@ -2,7 +2,10 @@ #include #include #include -#include + +#ifdef _MSC_VER +#include /* For struct timeval */ +#endif int main(int argc, char **argv) { unsigned int j, isunix = 0; diff --git a/fmacros.h b/fmacros.h index 3227faafd0ff..754a53c21e2a 100644 --- a/fmacros.h +++ b/fmacros.h @@ -1,8 +1,10 @@ #ifndef __HIREDIS_FMACRO_H #define __HIREDIS_FMACRO_H +#ifndef _AIX #define _XOPEN_SOURCE 600 #define _POSIX_C_SOURCE 200112L +#endif #if defined(__APPLE__) && defined(__MACH__) /* Enable TCP_KEEPALIVE */ diff --git a/fuzzing/format_command_fuzzer.c b/fuzzing/format_command_fuzzer.c new file mode 100644 index 000000000000..91adeac58a51 --- /dev/null +++ b/fuzzing/format_command_fuzzer.c @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2020, Salvatore Sanfilippo + * Copyright (c) 2020, Pieter Noordhuis + * Copyright (c) 2020, Matt Stancliff , + * Jan-Erik Rediger + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of Redis 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 OWNER 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. + */ + +#include +#include +#include "hiredis.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'; + + redisFormatCommand(&cmd, new_str); + + if (cmd != NULL) + hi_free(cmd); + free(new_str); + return 0; +} diff --git a/hiredis.c b/hiredis.c index 38e7e950aa7f..91086f6f6419 100644 --- a/hiredis.c +++ b/hiredis.c @@ -96,6 +96,8 @@ void freeReplyObject(void *reply) { switch(r->type) { case REDIS_REPLY_INTEGER: + case REDIS_REPLY_NIL: + case REDIS_REPLY_BOOL: break; /* Nothing to free */ case REDIS_REPLY_ARRAY: case REDIS_REPLY_MAP: @@ -112,6 +114,7 @@ void freeReplyObject(void *reply) { case REDIS_REPLY_STRING: case REDIS_REPLY_DOUBLE: case REDIS_REPLY_VERB: + case REDIS_REPLY_BIGNUM: hi_free(r->str); break; } @@ -129,7 +132,8 @@ static void *createStringObject(const redisReadTask *task, char *str, size_t len assert(task->type == REDIS_REPLY_ERROR || task->type == REDIS_REPLY_STATUS || task->type == REDIS_REPLY_STRING || - task->type == REDIS_REPLY_VERB); + task->type == REDIS_REPLY_VERB || + task->type == REDIS_REPLY_BIGNUM); /* Copy string value */ if (task->type == REDIS_REPLY_VERB) { @@ -235,12 +239,14 @@ static void *createDoubleObject(const redisReadTask *task, double value, char *s * 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 == REDIS_REPLY_ARRAY || parent->type == REDIS_REPLY_MAP || - parent->type == REDIS_REPLY_SET); + parent->type == REDIS_REPLY_SET || + parent->type == REDIS_REPLY_PUSH); parent->element[task->idx] = r; } return r; @@ -257,7 +263,8 @@ static void *createNilObject(const redisReadTask *task) { parent = task->parent->obj; assert(parent->type == REDIS_REPLY_ARRAY || parent->type == REDIS_REPLY_MAP || - parent->type == REDIS_REPLY_SET); + parent->type == REDIS_REPLY_SET || + parent->type == REDIS_REPLY_PUSH); parent->element[task->idx] = r; } return r; @@ -276,7 +283,8 @@ static void *createBoolObject(const redisReadTask *task, int bval) { parent = task->parent->obj; assert(parent->type == REDIS_REPLY_ARRAY || parent->type == REDIS_REPLY_MAP || - parent->type == REDIS_REPLY_SET); + parent->type == REDIS_REPLY_SET || + parent->type == REDIS_REPLY_PUSH); parent->element[task->idx] = r; } return r; @@ -305,7 +313,7 @@ int redisvFormatCommand(char **target, const char *format, va_list ap) { const char *c = format; char *cmd = NULL; /* final command */ int pos; /* position in final command */ - hisds curarg, newarg; /* current argument */ + sds curarg, newarg; /* current argument */ int touched = 0; /* was the current argument touched? */ char **curargv = NULL, **newargv = NULL; int argc = 0; @@ -318,7 +326,7 @@ int redisvFormatCommand(char **target, const char *format, va_list ap) { return -1; /* Build the command string accordingly to protocol */ - curarg = hi_sdsempty(); + curarg = sdsempty(); if (curarg == NULL) return -1; @@ -330,15 +338,15 @@ int redisvFormatCommand(char **target, const char *format, va_list ap) { if (newargv == NULL) goto memory_err; curargv = newargv; curargv[argc++] = curarg; - totlen += bulklen(hi_sdslen(curarg)); + totlen += bulklen(sdslen(curarg)); /* curarg is put in argv so it can be overwritten. */ - curarg = hi_sdsempty(); + curarg = sdsempty(); if (curarg == NULL) goto memory_err; touched = 0; } } else { - newarg = hi_sdscatlen(curarg,c,1); + newarg = sdscatlen(curarg,c,1); if (newarg == NULL) goto memory_err; curarg = newarg; touched = 1; @@ -355,16 +363,16 @@ int redisvFormatCommand(char **target, const char *format, va_list ap) { arg = va_arg(ap,char*); size = strlen(arg); if (size > 0) - newarg = hi_sdscatlen(curarg,arg,size); + newarg = sdscatlen(curarg,arg,size); break; case 'b': arg = va_arg(ap,char*); size = va_arg(ap,size_t); if (size > 0) - newarg = hi_sdscatlen(curarg,arg,size); + newarg = sdscatlen(curarg,arg,size); break; case '%': - newarg = hi_sdscat(curarg,"%"); + newarg = sdscat(curarg,"%"); break; default: /* Try to detect printf format */ @@ -452,7 +460,7 @@ int redisvFormatCommand(char **target, const char *format, va_list ap) { if (_l < sizeof(_format)-2) { memcpy(_format,c,_l); _format[_l] = '\0'; - newarg = hi_sdscatvprintf(curarg,_format,_cpy); + newarg = sdscatvprintf(curarg,_format,_cpy); /* Update current position (note: outer blocks * increment c twice so compensate here) */ @@ -479,9 +487,9 @@ int redisvFormatCommand(char **target, const char *format, va_list ap) { if (newargv == NULL) goto memory_err; curargv = newargv; curargv[argc++] = curarg; - totlen += bulklen(hi_sdslen(curarg)); + totlen += bulklen(sdslen(curarg)); } else { - hi_sdsfree(curarg); + sdsfree(curarg); } /* Clear curarg because it was put in curargv or was free'd. */ @@ -496,10 +504,10 @@ int redisvFormatCommand(char **target, const char *format, va_list ap) { pos = sprintf(cmd,"*%d\r\n",argc); for (j = 0; j < argc; j++) { - pos += sprintf(cmd+pos,"$%zu\r\n",hi_sdslen(curargv[j])); - memcpy(cmd+pos,curargv[j],hi_sdslen(curargv[j])); - pos += hi_sdslen(curargv[j]); - hi_sdsfree(curargv[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'; } @@ -521,11 +529,11 @@ int redisvFormatCommand(char **target, const char *format, va_list ap) { cleanup: if (curargv) { while(argc--) - hi_sdsfree(curargv[argc]); + sdsfree(curargv[argc]); hi_free(curargv); } - hi_sdsfree(curarg); + sdsfree(curarg); hi_free(cmd); return error_type; @@ -558,19 +566,18 @@ int redisFormatCommand(char **target, const char *format, ...) { return len; } -/* Format a command according to the Redis protocol using an hisds string and - * hi_sdscatfmt for the processing of arguments. This function takes the +/* Format a command according to the Redis 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. */ -int redisFormatSdsCommandArgv(hisds *target, int argc, const char **argv, - const size_t *argvlen) +long long redisFormatSdsCommandArgv(sds *target, int argc, const char **argv, + const size_t *argvlen) { - hisds cmd, aux; - unsigned long long totlen; + sds cmd, aux; + unsigned long long totlen, len; int j; - size_t len; /* Abort on a NULL target */ if (target == NULL) @@ -584,36 +591,36 @@ int redisFormatSdsCommandArgv(hisds *target, int argc, const char **argv, } /* Use an SDS string for command construction */ - cmd = hi_sdsempty(); + cmd = sdsempty(); if (cmd == NULL) return -1; /* We already know how much storage we need */ - aux = hi_sdsMakeRoomFor(cmd, totlen); + aux = sdsMakeRoomFor(cmd, totlen); if (aux == NULL) { - hi_sdsfree(cmd); + sdsfree(cmd); return -1; } cmd = aux; /* Construct command */ - cmd = hi_sdscatfmt(cmd, "*%i\r\n", argc); + cmd = sdscatfmt(cmd, "*%i\r\n", argc); for (j=0; j < argc; j++) { len = argvlen ? argvlen[j] : strlen(argv[j]); - cmd = hi_sdscatfmt(cmd, "$%u\r\n", len); - cmd = hi_sdscatlen(cmd, argv[j], len); - cmd = hi_sdscatlen(cmd, "\r\n", sizeof("\r\n")-1); + cmd = sdscatfmt(cmd, "$%U\r\n", len); + cmd = sdscatlen(cmd, argv[j], len); + cmd = sdscatlen(cmd, "\r\n", sizeof("\r\n")-1); } - assert(hi_sdslen(cmd)==totlen); + assert(sdslen(cmd)==totlen); *target = cmd; return totlen; } -void redisFreeSdsCommand(hisds cmd) { - hi_sdsfree(cmd); +void redisFreeSdsCommand(sds cmd) { + sdsfree(cmd); } /* Format a command according to the Redis protocol. This function takes the @@ -621,11 +628,11 @@ void redisFreeSdsCommand(hisds cmd) { * lengths. If the latter is set to NULL, strlen will be used to compute the * argument lengths. */ -int redisFormatCommandArgv(char **target, int argc, const char **argv, const size_t *argvlen) { +long long redisFormatCommandArgv(char **target, int argc, const char **argv, const size_t *argvlen) { char *cmd = NULL; /* final command */ - int pos; /* position in final command */ - size_t len; - int totlen, j; + size_t pos; /* position in final command */ + size_t len, totlen; + int j; /* Abort on a NULL target */ if (target == NULL) @@ -697,7 +704,7 @@ static redisContext *redisContextInit(void) { c->funcs = &redisContextDefaultFuncs; - c->obuf = hi_sdsempty(); + c->obuf = sdsempty(); c->reader = redisReaderCreate(); c->fd = REDIS_INVALID_FD; @@ -714,7 +721,7 @@ void redisFree(redisContext *c) { return; redisNetClose(c); - hi_sdsfree(c->obuf); + sdsfree(c->obuf); redisReaderFree(c->reader); hi_free(c->tcp.host); hi_free(c->tcp.source_addr); @@ -751,10 +758,10 @@ int redisReconnect(redisContext *c) { redisNetClose(c); - hi_sdsfree(c->obuf); + sdsfree(c->obuf); redisReaderFree(c->reader); - c->obuf = hi_sdsempty(); + c->obuf = sdsempty(); c->reader = redisReaderCreate(); if (c->obuf == NULL || c->reader == NULL) { @@ -796,6 +803,9 @@ redisContext *redisConnectWithOptions(const redisOptions *options) { if (options->options & REDIS_OPT_NOAUTOFREE) { c->flags |= REDIS_NO_AUTO_FREE; } + if (options->options & REDIS_OPT_NOAUTOFREEREPLIES) { + c->flags |= REDIS_NO_AUTO_FREE_REPLIES; + } /* Set any user supplied RESP3 PUSH handler or use freeReplyObject * as a default unless specifically flagged that we don't want one. */ @@ -824,7 +834,7 @@ redisContext *redisConnectWithOptions(const redisOptions *options) { c->fd = options->endpoint.fd; c->flags |= REDIS_CONNECTED; } else { - // Unknown type - FIXME - FREE + redisFree(c); return NULL; } @@ -938,13 +948,11 @@ int redisBufferRead(redisContext *c) { return REDIS_ERR; nread = c->funcs->read(c, buf, sizeof(buf)); - if (nread > 0) { - if (redisReaderFeed(c->reader, buf, nread) != REDIS_OK) { - __redisSetError(c, c->reader->err, c->reader->errstr); - return REDIS_ERR; - } else { - } - } else if (nread < 0) { + if (nread < 0) { + return REDIS_ERR; + } + if (nread > 0 && redisReaderFeed(c->reader, buf, nread) != REDIS_OK) { + __redisSetError(c, c->reader->err, c->reader->errstr); return REDIS_ERR; } return REDIS_OK; @@ -965,22 +973,22 @@ int redisBufferWrite(redisContext *c, int *done) { if (c->err) return REDIS_ERR; - if (hi_sdslen(c->obuf) > 0) { + if (sdslen(c->obuf) > 0) { ssize_t nwritten = c->funcs->write(c); if (nwritten < 0) { return REDIS_ERR; } else if (nwritten > 0) { - if (nwritten == (ssize_t)hi_sdslen(c->obuf)) { - hi_sdsfree(c->obuf); - c->obuf = hi_sdsempty(); + if (nwritten == (ssize_t)sdslen(c->obuf)) { + sdsfree(c->obuf); + c->obuf = sdsempty(); if (c->obuf == NULL) goto oom; } else { - if (hi_sdsrange(c->obuf,nwritten,-1) < 0) goto oom; + if (sdsrange(c->obuf,nwritten,-1) < 0) goto oom; } } } - if (done != NULL) *done = (hi_sdslen(c->obuf) == 0); + if (done != NULL) *done = (sdslen(c->obuf) == 0); return REDIS_OK; oom: @@ -988,17 +996,6 @@ int redisBufferWrite(redisContext *c, int *done) { return REDIS_ERR; } -/* Internal helper function to try and get a reply from the reader, - * or set an error in the context otherwise. */ -int redisGetReplyFromReader(redisContext *c, void **reply) { - if (redisReaderGetReply(c->reader,reply) == REDIS_ERR) { - __redisSetError(c,c->reader->err,c->reader->errstr); - return REDIS_ERR; - } - - return REDIS_OK; -} - /* Internal helper that returns 1 if the reply was a RESP3 PUSH * message and we handled it with a user-provided callback. */ static int redisHandledPushReply(redisContext *c, void *reply) { @@ -1010,12 +1007,34 @@ static int redisHandledPushReply(redisContext *c, void *reply) { return 0; } +/* Get a reply from our reader or set an error in the context. */ +int redisGetReplyFromReader(redisContext *c, void **reply) { + if (redisReaderGetReply(c->reader, reply) == REDIS_ERR) { + __redisSetError(c,c->reader->err,c->reader->errstr); + return REDIS_ERR; + } + + return REDIS_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 + * redisGetReplyFromReader so as to not change its behavior. */ +static int redisNextInBandReplyFromReader(redisContext *c, void **reply) { + do { + if (redisGetReplyFromReader(c, reply) == REDIS_ERR) + return REDIS_ERR; + } while (redisHandledPushReply(c, *reply)); + + return REDIS_OK; +} + int redisGetReply(redisContext *c, void **reply) { int wdone = 0; void *aux = NULL; /* Try to read pending replies */ - if (redisGetReplyFromReader(c,&aux) == REDIS_ERR) + if (redisNextInBandReplyFromReader(c,&aux) == REDIS_ERR) return REDIS_ERR; /* For the blocking context, flush output buffer and read reply */ @@ -1031,12 +1050,8 @@ int redisGetReply(redisContext *c, void **reply) { if (redisBufferRead(c) == REDIS_ERR) return REDIS_ERR; - /* We loop here in case the user has specified a RESP3 - * PUSH handler (e.g. for client tracking). */ - do { - if (redisGetReplyFromReader(c,&aux) == REDIS_ERR) - return REDIS_ERR; - } while (redisHandledPushReply(c, aux)); + if (redisNextInBandReplyFromReader(c,&aux) == REDIS_ERR) + return REDIS_ERR; } while (aux == NULL); } @@ -1058,9 +1073,9 @@ int redisGetReply(redisContext *c, void **reply) { * the reply (or replies in pub/sub). */ int __redisAppendCommand(redisContext *c, const char *cmd, size_t len) { - hisds newbuf; + sds newbuf; - newbuf = hi_sdscatlen(c->obuf,cmd,len); + newbuf = sdscatlen(c->obuf,cmd,len); if (newbuf == NULL) { __redisSetError(c,REDIS_ERR_OOM,"Out of memory"); return REDIS_ERR; @@ -1112,8 +1127,8 @@ int redisAppendCommand(redisContext *c, const char *format, ...) { } int redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen) { - hisds cmd; - int len; + sds cmd; + long long len; len = redisFormatSdsCommandArgv(&cmd,argc,argv,argvlen); if (len == -1) { @@ -1122,11 +1137,11 @@ int redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const s } if (__redisAppendCommand(c,cmd,len) != REDIS_OK) { - hi_sdsfree(cmd); + sdsfree(cmd); return REDIS_ERR; } - hi_sdsfree(cmd); + sdsfree(cmd); return REDIS_OK; } diff --git a/hiredis.h b/hiredis.h index b597394d4f61..b378128b5fb5 100644 --- a/hiredis.h +++ b/hiredis.h @@ -42,13 +42,13 @@ struct timeval; /* forward declaration */ typedef long long ssize_t; #endif #include /* uintXX_t, etc */ -#include "sds.h" /* for hisds */ +#include "sds.h" /* for sds */ #include "alloc.h" /* for allocation wrappers */ #define HIREDIS_MAJOR 1 #define HIREDIS_MINOR 0 -#define HIREDIS_PATCH 0 -#define HIREDIS_SONAME 1.0.0 +#define HIREDIS_PATCH 3 +#define HIREDIS_SONAME 1.0.3-dev /* Connection type can be blocking or non-blocking and is set in the * least significant bit of the flags field in redisContext. */ @@ -80,12 +80,18 @@ typedef long long ssize_t; /* Flag that is set when we should set SO_REUSEADDR before calling bind() */ #define REDIS_REUSEADDR 0x80 +/* Flag that is set when the async connection supports push replies. */ +#define REDIS_SUPPORTS_PUSH 0x100 + /** * Flag that indicates the user does not want the context to * be automatically freed upon error */ #define REDIS_NO_AUTO_FREE 0x200 +/* Flag that indicates the user does not want replies to be automatically freed */ +#define REDIS_NO_AUTO_FREE_REPLIES 0x400 + #define REDIS_KEEPALIVE_INTERVAL 15 /* seconds */ /* number of times we retry to connect in the case of EADDRNOTAVAIL and @@ -112,7 +118,8 @@ typedef struct redisReply { double dval; /* The double when type is REDIS_REPLY_DOUBLE */ size_t len; /* Length of string */ char *str; /* Used for REDIS_REPLY_ERROR, REDIS_REPLY_STRING - REDIS_REPLY_VERB, and REDIS_REPLY_DOUBLE (in additional to dval). */ + REDIS_REPLY_VERB, REDIS_REPLY_DOUBLE (in additional to dval), + and REDIS_REPLY_BIGNUM. */ char vtype[4]; /* Used for REDIS_REPLY_VERB, contains the null terminated 3 character content type, such as "txt". */ size_t elements; /* number of elements, for REDIS_REPLY_ARRAY */ @@ -127,10 +134,10 @@ void freeReplyObject(void *reply); /* Functions to format a command according to the protocol. */ int redisvFormatCommand(char **target, const char *format, va_list ap); int redisFormatCommand(char **target, const char *format, ...); -int redisFormatCommandArgv(char **target, int argc, const char **argv, const size_t *argvlen); -int redisFormatSdsCommandArgv(hisds *target, int argc, const char ** argv, const size_t *argvlen); +long long redisFormatCommandArgv(char **target, int argc, const char **argv, const size_t *argvlen); +long long redisFormatSdsCommandArgv(sds *target, int argc, const char ** argv, const size_t *argvlen); void redisFreeCommand(char *cmd); -void redisFreeSdsCommand(hisds cmd); +void redisFreeSdsCommand(sds cmd); enum redisConnectionType { REDIS_CONN_TCP, @@ -152,6 +159,11 @@ struct redisSsl; /* Don't automatically intercept and free RESP3 PUSH replies. */ #define REDIS_OPT_NO_PUSH_AUTOFREE 0x08 +/** + * Don't automatically free replies + */ +#define REDIS_OPT_NOAUTOFREEREPLIES 0x10 + /* 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 @@ -255,7 +267,7 @@ typedef struct redisContext { } unix_sock; /* For non-blocking connect */ - struct sockadr *saddr; + struct sockaddr *saddr; size_t addrlen; /* Optional data and corresponding destructor users can use to provide diff --git a/hiredis.targets b/hiredis.targets new file mode 100644 index 000000000000..effd8a561411 --- /dev/null +++ b/hiredis.targets @@ -0,0 +1,11 @@ + + + + + $(MSBuildThisFileDirectory)\..\..\include;%(AdditionalIncludeDirectories) + + + $(MSBuildThisFileDirectory)\..\..\lib;%(AdditionalLibraryDirectories) + + + \ No newline at end of file diff --git a/hiredis_ssl.h b/hiredis_ssl.h index 604efe0c10dd..e3d3e1cf52c7 100644 --- a/hiredis_ssl.h +++ b/hiredis_ssl.h @@ -56,7 +56,9 @@ typedef enum { REDIS_SSL_CTX_CERT_KEY_REQUIRED, /* Client cert and key must both be specified or skipped */ REDIS_SSL_CTX_CA_CERT_LOAD_FAILED, /* Failed to load CA Certificate or CA Path */ REDIS_SSL_CTX_CLIENT_CERT_LOAD_FAILED, /* Failed to load client certificate */ - REDIS_SSL_CTX_PRIVATE_KEY_LOAD_FAILED /* Failed to load private key */ + REDIS_SSL_CTX_PRIVATE_KEY_LOAD_FAILED, /* Failed to load private key */ + REDIS_SSL_CTX_OS_CERTSTORE_OPEN_FAILED, /* Failed to open system certifcate store */ + REDIS_SSL_CTX_OS_CERT_ADD_FAILED /* Failed to add CA certificates obtained from system to the SSL context */ } redisSSLContextError; /** diff --git a/net.c b/net.c index 88f9aff25064..c6b0e5d8e81c 100644 --- a/net.c +++ b/net.c @@ -80,7 +80,7 @@ ssize_t redisNetRead(redisContext *c, char *buf, size_t bufcap) { } ssize_t redisNetWrite(redisContext *c) { - ssize_t nwritten = send(c->fd, c->obuf, hi_sdslen(c->obuf), 0); + ssize_t nwritten = send(c->fd, c->obuf, sdslen(c->obuf), 0); if (nwritten < 0) { if ((errno == EWOULDBLOCK && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) { /* Try again later */ diff --git a/read.c b/read.c index 682b9a6b947e..de62b9ab06ec 100644 --- a/read.c +++ b/read.c @@ -59,7 +59,7 @@ static void __redisReaderSetError(redisReader *r, int type, const char *str) { } /* Clear input buffer on errors. */ - hi_sdsfree(r->buf); + sdsfree(r->buf); r->buf = NULL; r->pos = r->len = 0; @@ -123,29 +123,28 @@ static char *readBytes(redisReader *r, unsigned int bytes) { /* Find pointer to \r\n. */ static char *seekNewline(char *s, size_t len) { - int pos = 0; - int _len = len-1; - - /* Position should be < len-1 because the character at "pos" should be - * followed by a \n. Note that strchr cannot be used because it doesn't - * allow to search a limited length and the buffer that is being searched - * might not have a trailing NULL character. */ - while (pos < _len) { - while(pos < _len && s[pos] != '\r') pos++; - if (pos==_len) { - /* Not found. */ - return NULL; - } else { - if (s[pos+1] == '\n') { - /* Found. */ - return s+pos; - } else { - /* Continue searching. */ - pos++; - } + 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 NULL; + + return ret; } /* Convert a string into a long long. Returns REDIS_OK if the string could be @@ -274,60 +273,104 @@ static int processLineItem(redisReader *r) { if ((p = readLine(r,&len)) != NULL) { if (cur->type == REDIS_REPLY_INTEGER) { + long long v; + + if (string2ll(p, len, &v) == REDIS_ERR) { + __redisReaderSetError(r,REDIS_ERR_PROTOCOL, + "Bad integer value"); + return REDIS_ERR; + } + if (r->fn && r->fn->createInteger) { - long long v; - if (string2ll(p, len, &v) == REDIS_ERR) { - __redisReaderSetError(r,REDIS_ERR_PROTOCOL, - "Bad integer value"); - return REDIS_ERR; - } obj = r->fn->createInteger(cur,v); } else { obj = (void*)REDIS_REPLY_INTEGER; } } else if (cur->type == REDIS_REPLY_DOUBLE) { - if (r->fn && r->fn->createDouble) { - char buf[326], *eptr; - double d; + char buf[326], *eptr; + double d; - if ((size_t)len >= sizeof(buf)) { + if ((size_t)len >= sizeof(buf)) { + __redisReaderSetError(r,REDIS_ERR_PROTOCOL, + "Double value is too large"); + return REDIS_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 { + d = strtod((char*)buf,&eptr); + /* RESP3 only allows "inf", "-inf", and finite values, while + * strtod() allows other variations on infinity, NaN, + * etc. We explicity handle our two allowed infinite cases + * above, so strtod() should only result in finite values. */ + if (buf[0] == '\0' || eptr != &buf[len] || !isfinite(d)) { __redisReaderSetError(r,REDIS_ERR_PROTOCOL, - "Double value is too large"); + "Bad double value"); return REDIS_ERR; } + } - memcpy(buf,p,len); - buf[len] = '\0'; - - if (strcasecmp(buf,",inf") == 0) { - d = INFINITY; /* Positive infinite. */ - } else if (strcasecmp(buf,",-inf") == 0) { - d = -INFINITY; /* Negative infinite. */ - } else { - d = strtod((char*)buf,&eptr); - if (buf[0] == '\0' || eptr[0] != '\0' || isnan(d)) { - __redisReaderSetError(r,REDIS_ERR_PROTOCOL, - "Bad double value"); - return REDIS_ERR; - } - } + if (r->fn && r->fn->createDouble) { obj = r->fn->createDouble(cur,d,buf,len); } else { obj = (void*)REDIS_REPLY_DOUBLE; } } else if (cur->type == REDIS_REPLY_NIL) { + if (len != 0) { + __redisReaderSetError(r,REDIS_ERR_PROTOCOL, + "Bad nil value"); + return REDIS_ERR; + } + if (r->fn && r->fn->createNil) obj = r->fn->createNil(cur); else obj = (void*)REDIS_REPLY_NIL; } else if (cur->type == REDIS_REPLY_BOOL) { - int bval = p[0] == 't' || p[0] == 'T'; + int bval; + + if (len != 1 || !strchr("tTfF", p[0])) { + __redisReaderSetError(r,REDIS_ERR_PROTOCOL, + "Bad bool value"); + return REDIS_ERR; + } + + bval = p[0] == 't' || p[0] == 'T'; if (r->fn && r->fn->createBool) obj = r->fn->createBool(cur,bval); else obj = (void*)REDIS_REPLY_BOOL; + } else if (cur->type == REDIS_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') { + __redisReaderSetError(r,REDIS_ERR_PROTOCOL, + "Bad bignum value"); + return REDIS_ERR; + } + } + if (r->fn && r->fn->createString) + obj = r->fn->createString(cur,p,len); + else + obj = (void*)REDIS_REPLY_BIGNUM; } else { /* Type will be error or status. */ + for (int i = 0; i < len; i++) { + if (p[i] == '\r' || p[i] == '\n') { + __redisReaderSetError(r,REDIS_ERR_PROTOCOL, + "Bad simple string value"); + return REDIS_ERR; + } + } if (r->fn && r->fn->createString) obj = r->fn->createString(cur,p,len); else @@ -453,7 +496,6 @@ static int processAggregateItem(redisReader *r) { long long elements; int root = 0, len; - /* Set error for nested multi bulks with depth > 7 */ if (r->ridx == r->tasks - 1) { if (redisReaderGrow(r) == REDIS_ERR) return REDIS_ERR; @@ -569,6 +611,9 @@ static int processItem(redisReader *r) { case '>': cur->type = REDIS_REPLY_PUSH; break; + case '(': + cur->type = REDIS_REPLY_BIGNUM; + break; default: __redisReaderSetErrorProtocolByte(r,*p); return REDIS_ERR; @@ -587,6 +632,7 @@ static int processItem(redisReader *r) { case REDIS_REPLY_DOUBLE: case REDIS_REPLY_NIL: case REDIS_REPLY_BOOL: + case REDIS_REPLY_BIGNUM: return processLineItem(r); case REDIS_REPLY_STRING: case REDIS_REPLY_VERB: @@ -609,7 +655,7 @@ redisReader *redisReaderCreateWithFunctions(redisReplyObjectFunctions *fn) { if (r == NULL) return NULL; - r->buf = hi_sdsempty(); + r->buf = sdsempty(); if (r->buf == NULL) goto oom; @@ -650,12 +696,12 @@ void redisReaderFree(redisReader *r) { hi_free(r->task); } - hi_sdsfree(r->buf); + sdsfree(r->buf); hi_free(r); } int redisReaderFeed(redisReader *r, const char *buf, size_t len) { - hisds newbuf; + sds newbuf; /* Return early when this reader is in an erroneous state. */ if (r->err) @@ -664,19 +710,19 @@ int redisReaderFeed(redisReader *r, const char *buf, size_t len) { /* 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 && hi_sdsavail(r->buf) > r->maxbuf) { - hi_sdsfree(r->buf); - r->buf = hi_sdsempty(); + 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 = hi_sdscatlen(r->buf,buf,len); + newbuf = sdscatlen(r->buf,buf,len); if (newbuf == NULL) goto oom; r->buf = newbuf; - r->len = hi_sdslen(r->buf); + r->len = sdslen(r->buf); } return REDIS_OK; @@ -721,9 +767,9 @@ int redisReaderGetReply(redisReader *r, void **reply) { /* 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 (hi_sdsrange(r->buf,r->pos,-1) < 0) return REDIS_ERR; + if (sdsrange(r->buf,r->pos,-1) < 0) return REDIS_ERR; r->pos = 0; - r->len = hi_sdslen(r->buf); + r->len = sdslen(r->buf); } /* Emit a reply when there is one. */ diff --git a/sds.c b/sds.c index 675e7649fa4e..35baa057eb91 100644 --- a/sds.c +++ b/sds.c @@ -40,90 +40,90 @@ #include "sds.h" #include "sdsalloc.h" -static inline int hi_sdsHdrSize(char type) { - switch(type&HI_SDS_TYPE_MASK) { - case HI_SDS_TYPE_5: - return sizeof(struct hisdshdr5); - case HI_SDS_TYPE_8: - return sizeof(struct hisdshdr8); - case HI_SDS_TYPE_16: - return sizeof(struct hisdshdr16); - case HI_SDS_TYPE_32: - return sizeof(struct hisdshdr32); - case HI_SDS_TYPE_64: - return sizeof(struct hisdshdr64); +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 hi_sdsReqType(size_t string_size) { +static inline char sdsReqType(size_t string_size) { if (string_size < 32) - return HI_SDS_TYPE_5; + return SDS_TYPE_5; if (string_size < 0xff) - return HI_SDS_TYPE_8; + return SDS_TYPE_8; if (string_size < 0xffff) - return HI_SDS_TYPE_16; + return SDS_TYPE_16; if (string_size < 0xffffffff) - return HI_SDS_TYPE_32; - return HI_SDS_TYPE_64; + return SDS_TYPE_32; + return SDS_TYPE_64; } -/* Create a new hisds string with the content specified by the 'init' pointer +/* 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-termined (all the hisds strings are, always) so - * even if you create an hisds string with: + * The string is always null-terminated (all the sds strings are, always) so + * even if you create an sds string with: * - * mystring = hi_sdsnewlen("abc",3); + * 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 hisds header. */ -hisds hi_sdsnewlen(const void *init, size_t initlen) { + * \0 characters in the middle, as the length is stored in the sds header. */ +sds sdsnewlen(const void *init, size_t initlen) { void *sh; - hisds s; - char type = hi_sdsReqType(initlen); + 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 == HI_SDS_TYPE_5 && initlen == 0) type = HI_SDS_TYPE_8; - int hdrlen = hi_sdsHdrSize(type); + if (type == SDS_TYPE_5 && initlen == 0) type = SDS_TYPE_8; + int hdrlen = sdsHdrSize(type); unsigned char *fp; /* flags pointer. */ - sh = hi_s_malloc(hdrlen+initlen+1); + 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 HI_SDS_TYPE_5: { - *fp = type | (initlen << HI_SDS_TYPE_BITS); + case SDS_TYPE_5: { + *fp = type | (initlen << SDS_TYPE_BITS); break; } - case HI_SDS_TYPE_8: { - HI_SDS_HDR_VAR(8,s); + case SDS_TYPE_8: { + SDS_HDR_VAR(8,s); sh->len = initlen; sh->alloc = initlen; *fp = type; break; } - case HI_SDS_TYPE_16: { - HI_SDS_HDR_VAR(16,s); + case SDS_TYPE_16: { + SDS_HDR_VAR(16,s); sh->len = initlen; sh->alloc = initlen; *fp = type; break; } - case HI_SDS_TYPE_32: { - HI_SDS_HDR_VAR(32,s); + case SDS_TYPE_32: { + SDS_HDR_VAR(32,s); sh->len = initlen; sh->alloc = initlen; *fp = type; break; } - case HI_SDS_TYPE_64: { - HI_SDS_HDR_VAR(64,s); + case SDS_TYPE_64: { + SDS_HDR_VAR(64,s); sh->len = initlen; sh->alloc = initlen; *fp = type; @@ -136,164 +136,164 @@ hisds hi_sdsnewlen(const void *init, size_t initlen) { return s; } -/* Create an empty (zero length) hisds string. Even in this case the string +/* Create an empty (zero length) sds string. Even in this case the string * always has an implicit null term. */ -hisds hi_sdsempty(void) { - return hi_sdsnewlen("",0); +sds sdsempty(void) { + return sdsnewlen("",0); } -/* Create a new hisds string starting from a null terminated C string. */ -hisds hi_sdsnew(const char *init) { +/* 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 hi_sdsnewlen(init, initlen); + return sdsnewlen(init, initlen); } -/* Duplicate an hisds string. */ -hisds hi_sdsdup(const hisds s) { - return hi_sdsnewlen(s, hi_sdslen(s)); +/* Duplicate an sds string. */ +sds sdsdup(const sds s) { + return sdsnewlen(s, sdslen(s)); } -/* Free an hisds string. No operation is performed if 's' is NULL. */ -void hi_sdsfree(hisds s) { +/* Free an sds string. No operation is performed if 's' is NULL. */ +void sdsfree(sds s) { if (s == NULL) return; - hi_s_free((char*)s-hi_sdsHdrSize(s[-1])); + s_free((char*)s-sdsHdrSize(s[-1])); } -/* Set the hisds string length to the length as obtained with strlen(), so +/* 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 hisds string is hacked manually in some + * This function is useful when the sds string is hacked manually in some * way, like in the following example: * - * s = hi_sdsnew("foobar"); + * s = sdsnew("foobar"); * s[2] = '\0'; - * hi_sdsupdatelen(s); - * printf("%d\n", hi_sdslen(s)); + * sdsupdatelen(s); + * printf("%d\n", sdslen(s)); * - * The output will be "2", but if we comment out the call to hi_sdsupdatelen() + * 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 hi_sdsupdatelen(hisds s) { +void sdsupdatelen(sds s) { int reallen = strlen(s); - hi_sdssetlen(s, reallen); + sdssetlen(s, reallen); } -/* Modify an hisds string in-place to make it empty (zero length). +/* 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 hi_sdsclear(hisds s) { - hi_sdssetlen(s, 0); +void sdsclear(sds s) { + sdssetlen(s, 0); s[0] = '\0'; } -/* Enlarge the free space at the end of the hisds string so that the caller +/* 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 hisds string as returned - * by hi_sdslen(), but only the free buffer space we have. */ -hisds hi_sdsMakeRoomFor(hisds s, size_t addlen) { + * 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 = hi_sdsavail(s); + size_t avail = sdsavail(s); size_t len, newlen; - char type, oldtype = s[-1] & HI_SDS_TYPE_MASK; + char type, oldtype = s[-1] & SDS_TYPE_MASK; int hdrlen; /* Return ASAP if there is enough space left. */ if (avail >= addlen) return s; - len = hi_sdslen(s); - sh = (char*)s-hi_sdsHdrSize(oldtype); + len = sdslen(s); + sh = (char*)s-sdsHdrSize(oldtype); newlen = (len+addlen); - if (newlen < HI_SDS_MAX_PREALLOC) + if (newlen < SDS_MAX_PREALLOC) newlen *= 2; else - newlen += HI_SDS_MAX_PREALLOC; + newlen += SDS_MAX_PREALLOC; - type = hi_sdsReqType(newlen); + 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 hi_sdsMakeRoomFor() must be called + * not able to remember empty space, so sdsMakeRoomFor() must be called * at every appending operation. */ - if (type == HI_SDS_TYPE_5) type = HI_SDS_TYPE_8; + if (type == SDS_TYPE_5) type = SDS_TYPE_8; - hdrlen = hi_sdsHdrSize(type); + hdrlen = sdsHdrSize(type); if (oldtype==type) { - newsh = hi_s_realloc(sh, hdrlen+newlen+1); + 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 = hi_s_malloc(hdrlen+newlen+1); + newsh = s_malloc(hdrlen+newlen+1); if (newsh == NULL) return NULL; memcpy((char*)newsh+hdrlen, s, len+1); - hi_s_free(sh); + s_free(sh); s = (char*)newsh+hdrlen; s[-1] = type; - hi_sdssetlen(s, len); + sdssetlen(s, len); } - hi_sdssetalloc(s, newlen); + sdssetalloc(s, newlen); return s; } -/* Reallocate the hisds string so that it has no free space at the end. The +/* 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 hisds string is no longer valid and all the + * 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. */ -hisds hi_sdsRemoveFreeSpace(hisds s) { +sds sdsRemoveFreeSpace(sds s) { void *sh, *newsh; - char type, oldtype = s[-1] & HI_SDS_TYPE_MASK; + char type, oldtype = s[-1] & SDS_TYPE_MASK; int hdrlen; - size_t len = hi_sdslen(s); - sh = (char*)s-hi_sdsHdrSize(oldtype); + size_t len = sdslen(s); + sh = (char*)s-sdsHdrSize(oldtype); - type = hi_sdsReqType(len); - hdrlen = hi_sdsHdrSize(type); + type = sdsReqType(len); + hdrlen = sdsHdrSize(type); if (oldtype==type) { - newsh = hi_s_realloc(sh, hdrlen+len+1); + newsh = s_realloc(sh, hdrlen+len+1); if (newsh == NULL) return NULL; s = (char*)newsh+hdrlen; } else { - newsh = hi_s_malloc(hdrlen+len+1); + newsh = s_malloc(hdrlen+len+1); if (newsh == NULL) return NULL; memcpy((char*)newsh+hdrlen, s, len+1); - hi_s_free(sh); + s_free(sh); s = (char*)newsh+hdrlen; s[-1] = type; - hi_sdssetlen(s, len); + sdssetlen(s, len); } - hi_sdssetalloc(s, len); + sdssetalloc(s, len); return s; } -/* Return the total size of the allocation of the specifed hisds string, +/* Return the total size of the allocation of the specifed sds string, * including: - * 1) The hisds header before the pointer. + * 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 hi_sdsAllocSize(hisds s) { - size_t alloc = hi_sdsalloc(s); - return hi_sdsHdrSize(s[-1])+alloc+1; +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 *hi_sdsAllocPtr(hisds s) { - return (void*) (s-hi_sdsHdrSize(s[-1])); +void *sdsAllocPtr(sds s) { + return (void*) (s-sdsHdrSize(s[-1])); } -/* Increment the hisds length and decrements the left free space at the +/* 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 hi_sdsMakeRoomFor(), writes something after the end of + * 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 @@ -301,48 +301,48 @@ void *hi_sdsAllocPtr(hisds s) { * * Usage example: * - * Using hi_sdsIncrLen() and hi_sdsMakeRoomFor() it is possible to mount the + * Using sdsIncrLen() and sdsMakeRoomFor() it is possible to mount the * following schema, to cat bytes coming from the kernel to the end of an - * hisds string without copying into an intermediate buffer: + * sds string without copying into an intermediate buffer: * - * oldlen = hi_hi_sdslen(s); - * s = hi_sdsMakeRoomFor(s, BUFFER_SIZE); + * oldlen = sdslen(s); + * s = sdsMakeRoomFor(s, BUFFER_SIZE); * nread = read(fd, s+oldlen, BUFFER_SIZE); * ... check for nread <= 0 and handle it ... - * hi_sdsIncrLen(s, nread); + * sdsIncrLen(s, nread); */ -void hi_sdsIncrLen(hisds s, int incr) { +void sdsIncrLen(sds s, int incr) { unsigned char flags = s[-1]; size_t len; - switch(flags&HI_SDS_TYPE_MASK) { - case HI_SDS_TYPE_5: { + switch(flags&SDS_TYPE_MASK) { + case SDS_TYPE_5: { unsigned char *fp = ((unsigned char*)s)-1; - unsigned char oldlen = HI_SDS_TYPE_5_LEN(flags); + unsigned char oldlen = SDS_TYPE_5_LEN(flags); assert((incr > 0 && oldlen+incr < 32) || (incr < 0 && oldlen >= (unsigned int)(-incr))); - *fp = HI_SDS_TYPE_5 | ((oldlen+incr) << HI_SDS_TYPE_BITS); + *fp = SDS_TYPE_5 | ((oldlen+incr) << SDS_TYPE_BITS); len = oldlen+incr; break; } - case HI_SDS_TYPE_8: { - HI_SDS_HDR_VAR(8,s); + 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 HI_SDS_TYPE_16: { - HI_SDS_HDR_VAR(16,s); + 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 HI_SDS_TYPE_32: { - HI_SDS_HDR_VAR(32,s); + 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 HI_SDS_TYPE_64: { - HI_SDS_HDR_VAR(64,s); + 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; @@ -352,83 +352,83 @@ void hi_sdsIncrLen(hisds s, int incr) { s[len] = '\0'; } -/* Grow the hisds to have the specified length. Bytes that were not part of - * the original length of the hisds will be set to zero. +/* 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. */ -hisds hi_sdsgrowzero(hisds s, size_t len) { - size_t curlen = hi_sdslen(s); +sds sdsgrowzero(sds s, size_t len) { + size_t curlen = sdslen(s); if (len <= curlen) return s; - s = hi_sdsMakeRoomFor(s,len-curlen); + 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 */ - hi_sdssetlen(s, len); + sdssetlen(s, len); return s; } /* Append the specified binary-safe string pointed by 't' of 'len' bytes to the - * end of the specified hisds string 's'. + * end of the specified sds string 's'. * - * After the call, the passed hisds string is no longer valid and all the + * 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. */ -hisds hi_sdscatlen(hisds s, const void *t, size_t len) { - size_t curlen = hi_sdslen(s); +sds sdscatlen(sds s, const void *t, size_t len) { + size_t curlen = sdslen(s); - s = hi_sdsMakeRoomFor(s,len); + s = sdsMakeRoomFor(s,len); if (s == NULL) return NULL; memcpy(s+curlen, t, len); - hi_sdssetlen(s, curlen+len); + sdssetlen(s, curlen+len); s[curlen+len] = '\0'; return s; } -/* Append the specified null termianted C string to the hisds string 's'. +/* Append the specified null termianted C string to the sds string 's'. * - * After the call, the passed hisds string is no longer valid and all the + * 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. */ -hisds hi_sdscat(hisds s, const char *t) { - return hi_sdscatlen(s, t, strlen(t)); +sds sdscat(sds s, const char *t) { + return sdscatlen(s, t, strlen(t)); } -/* Append the specified hisds 't' to the existing hisds 's'. +/* Append the specified sds 't' to the existing sds 's'. * - * After the call, the modified hisds string is no longer valid and all the + * 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. */ -hisds hi_sdscatsds(hisds s, const hisds t) { - return hi_sdscatlen(s, t, hi_sdslen(t)); +sds sdscatsds(sds s, const sds t) { + return sdscatlen(s, t, sdslen(t)); } -/* Destructively modify the hisds string 's' to hold the specified binary +/* Destructively modify the sds string 's' to hold the specified binary * safe string pointed by 't' of length 'len' bytes. */ -hisds hi_sdscpylen(hisds s, const char *t, size_t len) { - if (hi_sdsalloc(s) < len) { - s = hi_sdsMakeRoomFor(s,len-hi_sdslen(s)); +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'; - hi_sdssetlen(s, len); + sdssetlen(s, len); return s; } -/* Like hi_sdscpylen() but 't' must be a null-termined string so that the length +/* Like sdscpylen() but 't' must be a null-terminated string so that the length * of the string is obtained with strlen(). */ -hisds hi_sdscpy(hisds s, const char *t) { - return hi_sdscpylen(s, t, strlen(t)); +sds sdscpy(sds s, const char *t) { + return sdscpylen(s, t, strlen(t)); } -/* Helper for hi_sdscatlonglong() doing the actual number -> string +/* Helper for sdscatlonglong() doing the actual number -> string * conversion. 's' must point to a string with room for at least - * HI_SDS_LLSTR_SIZE bytes. + * SDS_LLSTR_SIZE bytes. * * The function returns the length of the null-terminated string * representation stored at 's'. */ -#define HI_SDS_LLSTR_SIZE 21 -int hi_sdsll2str(char *s, long long value) { +#define SDS_LLSTR_SIZE 21 +int sdsll2str(char *s, long long value) { char *p, aux; unsigned long long v; size_t l; @@ -459,8 +459,8 @@ int hi_sdsll2str(char *s, long long value) { return l; } -/* Identical hi_sdsll2str(), but for unsigned long long type. */ -int hi_sdsull2str(char *s, unsigned long long v) { +/* Identical sdsll2str(), but for unsigned long long type. */ +int sdsull2str(char *s, unsigned long long v) { char *p, aux; size_t l; @@ -488,19 +488,19 @@ int hi_sdsull2str(char *s, unsigned long long v) { return l; } -/* Create an hisds string from a long long value. It is much faster than: +/* Create an sds string from a long long value. It is much faster than: * - * hi_sdscatprintf(hi_sdsempty(),"%lld\n", value); + * sdscatprintf(sdsempty(),"%lld\n", value); */ -hisds hi_sdsfromlonglong(long long value) { - char buf[HI_SDS_LLSTR_SIZE]; - int len = hi_sdsll2str(buf,value); +sds sdsfromlonglong(long long value) { + char buf[SDS_LLSTR_SIZE]; + int len = sdsll2str(buf,value); - return hi_sdsnewlen(buf,len); + return sdsnewlen(buf,len); } -/* Like hi_sdscatprintf() but gets va_list instead of being variadic. */ -hisds hi_sdscatvprintf(hisds s, const char *fmt, va_list ap) { +/* 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; @@ -508,7 +508,7 @@ hisds hi_sdscatvprintf(hisds s, const char *fmt, va_list ap) { /* We try to start using a static buffer for speed. * If not possible we revert to heap allocation. */ if (buflen > sizeof(staticbuf)) { - buf = hi_s_malloc(buflen); + buf = s_malloc(buflen); if (buf == NULL) return NULL; } else { buflen = sizeof(staticbuf); @@ -522,9 +522,9 @@ hisds hi_sdscatvprintf(hisds s, const char *fmt, va_list ap) { vsnprintf(buf, buflen, fmt, cpy); va_end(cpy); if (buf[buflen-2] != '\0') { - if (buf != staticbuf) hi_s_free(buf); + if (buf != staticbuf) s_free(buf); buflen *= 2; - buf = hi_s_malloc(buflen); + buf = s_malloc(buflen); if (buf == NULL) return NULL; continue; } @@ -532,39 +532,39 @@ hisds hi_sdscatvprintf(hisds s, const char *fmt, va_list ap) { } /* Finally concat the obtained string to the SDS string and return it. */ - t = hi_sdscat(s, buf); - if (buf != staticbuf) hi_s_free(buf); + t = sdscat(s, buf); + if (buf != staticbuf) s_free(buf); return t; } -/* Append to the hisds string 's' a string obtained using printf-alike format +/* Append to the sds string 's' a string obtained using printf-alike format * specifier. * - * After the call, the modified hisds string is no longer valid and all the + * 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 = hi_sdsnew("Sum is: "); - * s = hi_sdscatprintf(s,"%d+%d = %d",a,b,a+b). + * 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 hi_sdsempty() as the target string: + * format. When this is the need, just use sdsempty() as the target string: * - * s = hi_sdscatprintf(hi_sdsempty(), "... your format ...", args); + * s = sdscatprintf(sdsempty(), "... your format ...", args); */ -hisds hi_sdscatprintf(hisds s, const char *fmt, ...) { +sds sdscatprintf(sds s, const char *fmt, ...) { va_list ap; char *t; va_start(ap, fmt); - t = hi_sdscatvprintf(s,fmt,ap); + t = sdscatvprintf(s,fmt,ap); va_end(ap); return t; } -/* This function is similar to hi_sdscatprintf, but much faster as it does +/* 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 hisds string as + * 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 @@ -578,13 +578,13 @@ hisds hi_sdscatprintf(hisds s, const char *fmt, ...) { * %U - 64 bit unsigned integer (unsigned long long, uint64_t) * %% - Verbatim "%" character. */ -hisds hi_sdscatfmt(hisds s, char const *fmt, ...) { +sds sdscatfmt(sds s, char const *fmt, ...) { const char *f = fmt; int i; va_list ap; va_start(ap,fmt); - i = hi_sdslen(s); /* Position of the next byte to write to dest str. */ + i = sdslen(s); /* Position of the next byte to write to dest str. */ while(*f) { char next, *str; size_t l; @@ -592,8 +592,8 @@ hisds hi_sdscatfmt(hisds s, char const *fmt, ...) { unsigned long long unum; /* Make sure there is always space for at least 1 char. */ - if (hi_sdsavail(s)==0) { - s = hi_sdsMakeRoomFor(s,1); + if (sdsavail(s)==0) { + s = sdsMakeRoomFor(s,1); if (s == NULL) goto fmt_error; } @@ -605,13 +605,13 @@ hisds hi_sdscatfmt(hisds s, char const *fmt, ...) { case 's': case 'S': str = va_arg(ap,char*); - l = (next == 's') ? strlen(str) : hi_sdslen(str); - if (hi_sdsavail(s) < l) { - s = hi_sdsMakeRoomFor(s,l); + 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); - hi_sdsinclen(s,l); + sdsinclen(s,l); i += l; break; case 'i': @@ -621,14 +621,14 @@ hisds hi_sdscatfmt(hisds s, char const *fmt, ...) { else num = va_arg(ap,long long); { - char buf[HI_SDS_LLSTR_SIZE]; - l = hi_sdsll2str(buf,num); - if (hi_sdsavail(s) < l) { - s = hi_sdsMakeRoomFor(s,l); + 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); - hi_sdsinclen(s,l); + sdsinclen(s,l); i += l; } break; @@ -639,26 +639,26 @@ hisds hi_sdscatfmt(hisds s, char const *fmt, ...) { else unum = va_arg(ap,unsigned long long); { - char buf[HI_SDS_LLSTR_SIZE]; - l = hi_sdsull2str(buf,unum); - if (hi_sdsavail(s) < l) { - s = hi_sdsMakeRoomFor(s,l); + 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); - hi_sdsinclen(s,l); + sdsinclen(s,l); i += l; } break; default: /* Handle %% and generally %. */ s[i++] = next; - hi_sdsinclen(s,1); + sdsinclen(s,1); break; } break; default: s[i++] = *f; - hi_sdsinclen(s,1); + sdsinclen(s,1); break; } f++; @@ -677,29 +677,29 @@ hisds hi_sdscatfmt(hisds s, char const *fmt, ...) { /* 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 hisds string is no longer valid and all the + * 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 = hi_sdsnew("AA...AA.a.aa.aHelloWorld :::"); - * s = hi_sdstrim(s,"Aa. :"); + * s = sdsnew("AA...AA.a.aa.aHelloWorld :::"); + * s = sdstrim(s,"Aa. :"); * printf("%s\n", s); * * Output will be just "Hello World". */ -hisds hi_sdstrim(hisds s, const char *cset) { +sds sdstrim(sds s, const char *cset) { char *start, *end, *sp, *ep; size_t len; sp = start = s; - ep = end = s+hi_sdslen(s)-1; + 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'; - hi_sdssetlen(s,len); + sdssetlen(s,len); return s; } @@ -715,16 +715,16 @@ hisds hi_sdstrim(hisds s, const char *cset) { * The string is modified in-place. * * Return value: - * -1 (error) if hi_sdslen(s) is larger than maximum positive ssize_t value. + * -1 (error) if sdslen(s) is larger than maximum positive ssize_t value. * 0 on success. * * Example: * - * s = hi_sdsnew("Hello World"); - * hi_sdsrange(s,1,-1); => "ello World" + * s = sdsnew("Hello World"); + * sdsrange(s,1,-1); => "ello World" */ -int hi_sdsrange(hisds s, ssize_t start, ssize_t end) { - size_t newlen, len = hi_sdslen(s); +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; @@ -749,25 +749,25 @@ int hi_sdsrange(hisds s, ssize_t start, ssize_t end) { } if (start && newlen) memmove(s, s+start, newlen); s[newlen] = 0; - hi_sdssetlen(s,newlen); + sdssetlen(s,newlen); return 0; } -/* Apply tolower() to every character of the hisds string 's'. */ -void hi_sdstolower(hisds s) { - int len = hi_sdslen(s), j; +/* Apply tolower() to every character of the sds string 's'. */ +void sdstolower(sds s) { + int len = sdslen(s), j; for (j = 0; j < len; j++) s[j] = tolower(s[j]); } -/* Apply toupper() to every character of the hisds string 's'. */ -void hi_sdstoupper(hisds s) { - int len = hi_sdslen(s), j; +/* Apply toupper() to every character of the sds string 's'. */ +void sdstoupper(sds s) { + int len = sdslen(s), j; for (j = 0; j < len; j++) s[j] = toupper(s[j]); } -/* Compare two hisds strings s1 and s2 with memcmp(). +/* Compare two sds strings s1 and s2 with memcmp(). * * Return value: * @@ -778,12 +778,12 @@ void hi_sdstoupper(hisds s) { * 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 hi_sdscmp(const hisds s1, const hisds s2) { +int sdscmp(const sds s1, const sds s2) { size_t l1, l2, minlen; int cmp; - l1 = hi_sdslen(s1); - l2 = hi_sdslen(s2); + l1 = sdslen(s1); + l2 = sdslen(s2); minlen = (l1 < l2) ? l1 : l2; cmp = memcmp(s1,s2,minlen); if (cmp == 0) return l1-l2; @@ -791,7 +791,7 @@ int hi_sdscmp(const hisds s1, const hisds s2) { } /* Split 's' with separator in 'sep'. An array - * of hisds strings is returned. *count will be set + * 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 @@ -799,20 +799,20 @@ int hi_sdscmp(const hisds s1, const hisds s2) { * * Note that 'sep' is able to split a string using * a multi-character separator. For example - * hi_sdssplit("foo_-_bar","_-_"); will return two + * sdssplit("foo_-_bar","_-_"); will return two * elements "foo" and "bar". * * This version of the function is binary-safe but - * requires length arguments. hi_sdssplit() is just the + * requires length arguments. sdssplit() is just the * same function but for zero-terminated strings. */ -hisds *hi_sdssplitlen(const char *s, int len, const char *sep, int seplen, int *count) { +sds *sdssplitlen(const char *s, int len, const char *sep, int seplen, int *count) { int elements = 0, slots = 5, start = 0, j; - hisds *tokens; + sds *tokens; if (seplen < 1 || len < 0) return NULL; - tokens = hi_s_malloc(sizeof(hisds)*slots); + tokens = s_malloc(sizeof(sds)*slots); if (tokens == NULL) return NULL; if (len == 0) { @@ -822,16 +822,16 @@ hisds *hi_sdssplitlen(const char *s, int len, const char *sep, int seplen, int * 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) { - hisds *newtokens; + sds *newtokens; slots *= 2; - newtokens = hi_s_realloc(tokens,sizeof(hisds)*slots); + 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] = hi_sdsnewlen(s+start,j-start); + tokens[elements] = sdsnewlen(s+start,j-start); if (tokens[elements] == NULL) goto cleanup; elements++; start = j+seplen; @@ -839,7 +839,7 @@ hisds *hi_sdssplitlen(const char *s, int len, const char *sep, int seplen, int * } } /* Add the final element. We are sure there is room in the tokens array. */ - tokens[elements] = hi_sdsnewlen(s+start,len-start); + tokens[elements] = sdsnewlen(s+start,len-start); if (tokens[elements] == NULL) goto cleanup; elements++; *count = elements; @@ -848,55 +848,55 @@ hisds *hi_sdssplitlen(const char *s, int len, const char *sep, int seplen, int * cleanup: { int i; - for (i = 0; i < elements; i++) hi_sdsfree(tokens[i]); - hi_s_free(tokens); + for (i = 0; i < elements; i++) sdsfree(tokens[i]); + s_free(tokens); *count = 0; return NULL; } } -/* Free the result returned by hi_sdssplitlen(), or do nothing if 'tokens' is NULL. */ -void hi_sdsfreesplitres(hisds *tokens, int count) { +/* Free the result returned by sdssplitlen(), or do nothing if 'tokens' is NULL. */ +void sdsfreesplitres(sds *tokens, int count) { if (!tokens) return; while(count--) - hi_sdsfree(tokens[count]); - hi_s_free(tokens); + sdsfree(tokens[count]); + s_free(tokens); } -/* Append to the hisds string "s" an escaped string representation where +/* 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 hisds string is no longer valid and all the + * 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. */ -hisds hi_sdscatrepr(hisds s, const char *p, size_t len) { - s = hi_sdscatlen(s,"\"",1); +sds sdscatrepr(sds s, const char *p, size_t len) { + s = sdscatlen(s,"\"",1); while(len--) { switch(*p) { case '\\': case '"': - s = hi_sdscatprintf(s,"\\%c",*p); + s = sdscatprintf(s,"\\%c",*p); break; - case '\n': s = hi_sdscatlen(s,"\\n",2); break; - case '\r': s = hi_sdscatlen(s,"\\r",2); break; - case '\t': s = hi_sdscatlen(s,"\\t",2); break; - case '\a': s = hi_sdscatlen(s,"\\a",2); break; - case '\b': s = hi_sdscatlen(s,"\\b",2); 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(*p)) - s = hi_sdscatprintf(s,"%c",*p); + s = sdscatprintf(s,"%c",*p); else - s = hi_sdscatprintf(s,"\\x%02x",(unsigned char)*p); + s = sdscatprintf(s,"\\x%02x",(unsigned char)*p); break; } p++; } - return hi_sdscatlen(s,"\"",1); + return sdscatlen(s,"\"",1); } -/* Helper function for hi_sdssplitargs() that converts a hex digit into an +/* Helper function for sdssplitargs() that converts a hex digit into an * integer from 0 to 15 */ -static int hi_hex_digit_to_int(char c) { +int hex_digit_to_int(char c) { switch(c) { case '0': return 0; case '1': return 1; @@ -924,20 +924,20 @@ static int hi_hex_digit_to_int(char c) { * foo bar "newline are supported\n" and "\xff\x00otherstuff" * * The number of arguments is stored into *argc, and an array - * of hisds is returned. + * of sds is returned. * - * The caller should free the resulting array of hisds strings with - * hi_sdsfreesplitres(). + * The caller should free the resulting array of sds strings with + * sdsfreesplitres(). * - * Note that hi_sdscatrepr() is able to convert back a string into - * a quoted string in the same format hi_sdssplitargs() is able to parse. + * 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' */ -hisds *hi_sdssplitargs(const char *line, int *argc) { +sds *sdssplitargs(const char *line, int *argc) { const char *p = line; char *current = NULL; char **vector = NULL; @@ -952,7 +952,7 @@ hisds *hi_sdssplitargs(const char *line, int *argc) { int insq=0; /* set to 1 if we are in 'single quotes' */ int done=0; - if (current == NULL) current = hi_sdsempty(); + if (current == NULL) current = sdsempty(); while(!done) { if (inq) { if (*p == '\\' && *(p+1) == 'x' && @@ -961,9 +961,9 @@ hisds *hi_sdssplitargs(const char *line, int *argc) { { unsigned char byte; - byte = (hi_hex_digit_to_int(*(p+2))*16)+ - hi_hex_digit_to_int(*(p+3)); - current = hi_sdscatlen(current,(char*)&byte,1); + 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; @@ -977,7 +977,7 @@ hisds *hi_sdssplitargs(const char *line, int *argc) { case 'a': c = '\a'; break; default: c = *p; break; } - current = hi_sdscatlen(current,&c,1); + current = sdscatlen(current,&c,1); } else if (*p == '"') { /* closing quote must be followed by a space or * nothing at all. */ @@ -987,12 +987,12 @@ hisds *hi_sdssplitargs(const char *line, int *argc) { /* unterminated quotes */ goto err; } else { - current = hi_sdscatlen(current,p,1); + current = sdscatlen(current,p,1); } } else if (insq) { if (*p == '\\' && *(p+1) == '\'') { p++; - current = hi_sdscatlen(current,"'",1); + current = sdscatlen(current,"'",1); } else if (*p == '\'') { /* closing quote must be followed by a space or * nothing at all. */ @@ -1002,7 +1002,7 @@ hisds *hi_sdssplitargs(const char *line, int *argc) { /* unterminated quotes */ goto err; } else { - current = hi_sdscatlen(current,p,1); + current = sdscatlen(current,p,1); } } else { switch(*p) { @@ -1020,7 +1020,7 @@ hisds *hi_sdssplitargs(const char *line, int *argc) { insq=1; break; default: - current = hi_sdscatlen(current,p,1); + current = sdscatlen(current,p,1); break; } } @@ -1028,9 +1028,9 @@ hisds *hi_sdssplitargs(const char *line, int *argc) { } /* add the token to the vector */ { - char **new_vector = hi_s_realloc(vector,((*argc)+1)*sizeof(char*)); + char **new_vector = s_realloc(vector,((*argc)+1)*sizeof(char*)); if (new_vector == NULL) { - hi_s_free(vector); + s_free(vector); return NULL; } @@ -1041,16 +1041,16 @@ hisds *hi_sdssplitargs(const char *line, int *argc) { } } else { /* Even on empty input string return something not NULL. */ - if (vector == NULL) vector = hi_s_malloc(sizeof(void*)); + if (vector == NULL) vector = s_malloc(sizeof(void*)); return vector; } } err: while((*argc)--) - hi_sdsfree(vector[*argc]); - hi_s_free(vector); - if (current) hi_sdsfree(current); + sdsfree(vector[*argc]); + s_free(vector); + if (current) sdsfree(current); *argc = 0; return NULL; } @@ -1059,13 +1059,13 @@ hisds *hi_sdssplitargs(const char *line, int *argc) { * characters specified in the 'from' string to the corresponding character * in the 'to' array. * - * For instance: hi_sdsmapchars(mystring, "ho", "01", 2) + * For instance: sdsmapchars(mystring, "ho", "01", 2) * will have the effect of turning the string "hello" into "0ell1". * - * The function returns the hisds string pointer, that is always the same + * The function returns the sds string pointer, that is always the same * as the input pointer since no resize is needed. */ -hisds hi_sdsmapchars(hisds s, const char *from, const char *to, size_t setlen) { - size_t j, i, l = hi_sdslen(s); +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++) { @@ -1079,26 +1079,26 @@ hisds hi_sdsmapchars(hisds s, const char *from, const char *to, size_t setlen) { } /* Join an array of C strings using the specified separator (also a C string). - * Returns the result as an hisds string. */ -hisds hi_sdsjoin(char **argv, int argc, char *sep) { - hisds join = hi_sdsempty(); + * 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 = hi_sdscat(join, argv[j]); - if (j != argc-1) join = hi_sdscat(join,sep); + join = sdscat(join, argv[j]); + if (j != argc-1) join = sdscat(join,sep); } return join; } -/* Like hi_sdsjoin, but joins an array of SDS strings. */ -hisds hi_sdsjoinsds(hisds *argv, int argc, const char *sep, size_t seplen) { - hisds join = hi_sdsempty(); +/* 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 = hi_sdscatsds(join, argv[j]); - if (j != argc-1) join = hi_sdscatlen(join,sep,seplen); + join = sdscatsds(join, argv[j]); + if (j != argc-1) join = sdscatlen(join,sep,seplen); } return join; } @@ -1108,138 +1108,138 @@ hisds hi_sdsjoinsds(hisds *argv, int argc, const char *sep, size_t seplen) { * 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 *hi_sds_malloc(size_t size) { return hi_s_malloc(size); } -void *hi_sds_realloc(void *ptr, size_t size) { return hi_s_realloc(ptr,size); } -void hi_sds_free(void *ptr) { hi_s_free(ptr); } +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(HI_SDS_TEST_MAIN) +#if defined(SDS_TEST_MAIN) #include #include "testhelp.h" #include "limits.h" #define UNUSED(x) (void)(x) -int hi_sdsTest(void) { +int sdsTest(void) { { - hisds x = hi_sdsnew("foo"), y; + sds x = sdsnew("foo"), y; test_cond("Create a string and obtain the length", - hi_sdslen(x) == 3 && memcmp(x,"foo\0",4) == 0) + sdslen(x) == 3 && memcmp(x,"foo\0",4) == 0) - hi_sdsfree(x); - x = hi_sdsnewlen("foo",2); + sdsfree(x); + x = sdsnewlen("foo",2); test_cond("Create a string with specified length", - hi_sdslen(x) == 2 && memcmp(x,"fo\0",3) == 0) + sdslen(x) == 2 && memcmp(x,"fo\0",3) == 0) - x = hi_sdscat(x,"bar"); + x = sdscat(x,"bar"); test_cond("Strings concatenation", - hi_sdslen(x) == 5 && memcmp(x,"fobar\0",6) == 0); + sdslen(x) == 5 && memcmp(x,"fobar\0",6) == 0); - x = hi_sdscpy(x,"a"); - test_cond("hi_sdscpy() against an originally longer string", - hi_sdslen(x) == 1 && memcmp(x,"a\0",2) == 0) + x = sdscpy(x,"a"); + test_cond("sdscpy() against an originally longer string", + sdslen(x) == 1 && memcmp(x,"a\0",2) == 0) - x = hi_sdscpy(x,"xyzxxxxxxxxxxyyyyyyyyyykkkkkkkkkk"); - test_cond("hi_sdscpy() against an originally shorter string", - hi_sdslen(x) == 33 && + x = sdscpy(x,"xyzxxxxxxxxxxyyyyyyyyyykkkkkkkkkk"); + test_cond("sdscpy() against an originally shorter string", + sdslen(x) == 33 && memcmp(x,"xyzxxxxxxxxxxyyyyyyyyyykkkkkkkkkk\0",33) == 0) - hi_sdsfree(x); - x = hi_sdscatprintf(hi_sdsempty(),"%d",123); - test_cond("hi_sdscatprintf() seems working in the base case", - hi_sdslen(x) == 3 && memcmp(x,"123\0",4) == 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) - hi_sdsfree(x); - x = hi_sdsnew("--"); - x = hi_sdscatfmt(x, "Hello %s World %I,%I--", "Hi!", LLONG_MIN,LLONG_MAX); - test_cond("hi_sdscatfmt() seems working in the base case", - hi_sdslen(x) == 60 && + 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); - hi_sdsfree(x); - x = hi_sdsnew("--"); - x = hi_sdscatfmt(x, "%u,%U--", UINT_MAX, ULLONG_MAX); - test_cond("hi_sdscatfmt() seems working with unsigned numbers", - hi_sdslen(x) == 35 && + 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) - hi_sdsfree(x); - x = hi_sdsnew(" x "); - hi_sdstrim(x," x"); - test_cond("hi_sdstrim() works when all chars match", - hi_sdslen(x) == 0) - - hi_sdsfree(x); - x = hi_sdsnew(" x "); - hi_sdstrim(x," "); - test_cond("hi_sdstrim() works when a single char remains", - hi_sdslen(x) == 1 && x[0] == 'x') - - hi_sdsfree(x); - x = hi_sdsnew("xxciaoyyy"); - hi_sdstrim(x,"xy"); - test_cond("hi_sdstrim() correctly trims characters", - hi_sdslen(x) == 4 && memcmp(x,"ciao\0",5) == 0) - - y = hi_sdsdup(x); - hi_sdsrange(y,1,1); - test_cond("hi_sdsrange(...,1,1)", - hi_sdslen(y) == 1 && memcmp(y,"i\0",2) == 0) - - hi_sdsfree(y); - y = hi_sdsdup(x); - hi_sdsrange(y,1,-1); - test_cond("hi_sdsrange(...,1,-1)", - hi_sdslen(y) == 3 && memcmp(y,"iao\0",4) == 0) - - hi_sdsfree(y); - y = hi_sdsdup(x); - hi_sdsrange(y,-2,-1); - test_cond("hi_sdsrange(...,-2,-1)", - hi_sdslen(y) == 2 && memcmp(y,"ao\0",3) == 0) - - hi_sdsfree(y); - y = hi_sdsdup(x); - hi_sdsrange(y,2,1); - test_cond("hi_sdsrange(...,2,1)", - hi_sdslen(y) == 0 && memcmp(y,"\0",1) == 0) - - hi_sdsfree(y); - y = hi_sdsdup(x); - hi_sdsrange(y,1,100); - test_cond("hi_sdsrange(...,1,100)", - hi_sdslen(y) == 3 && memcmp(y,"iao\0",4) == 0) - - hi_sdsfree(y); - y = hi_sdsdup(x); - hi_sdsrange(y,100,100); - test_cond("hi_sdsrange(...,100,100)", - hi_sdslen(y) == 0 && memcmp(y,"\0",1) == 0) - - hi_sdsfree(y); - hi_sdsfree(x); - x = hi_sdsnew("foo"); - y = hi_sdsnew("foa"); - test_cond("hi_sdscmp(foo,foa)", hi_sdscmp(x,y) > 0) - - hi_sdsfree(y); - hi_sdsfree(x); - x = hi_sdsnew("bar"); - y = hi_sdsnew("bar"); - test_cond("hi_sdscmp(bar,bar)", hi_sdscmp(x,y) == 0) - - hi_sdsfree(y); - hi_sdsfree(x); - x = hi_sdsnew("aar"); - y = hi_sdsnew("bar"); - test_cond("hi_sdscmp(bar,bar)", hi_sdscmp(x,y) < 0) - - hi_sdsfree(y); - hi_sdsfree(x); - x = hi_sdsnewlen("\a\n\0foo\r",7); - y = hi_sdscatrepr(hi_sdsempty(),x,hi_sdslen(x)); - test_cond("hi_sdscatrepr(...data...)", + 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) { @@ -1247,43 +1247,43 @@ int hi_sdsTest(void) { char *p; int step = 10, j, i; - hi_sdsfree(x); - hi_sdsfree(y); - x = hi_sdsnew("0"); - test_cond("hi_sdsnew() free/len buffers", hi_sdslen(x) == 1 && hi_sdsavail(x) == 0); + 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 = hi_sdslen(x); - x = hi_sdsMakeRoomFor(x,step); - int type = x[-1]&HI_SDS_TYPE_MASK; - - test_cond("sdsMakeRoomFor() len", hi_sdslen(x) == oldlen); - if (type != HI_SDS_TYPE_5) { - test_cond("hi_sdsMakeRoomFor() free", hi_sdsavail(x) >= step); - oldfree = hi_sdsavail(x); + 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; } - hi_sdsIncrLen(x,step); + sdsIncrLen(x,step); } - test_cond("hi_sdsMakeRoomFor() content", + test_cond("sdsMakeRoomFor() content", memcmp("0ABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJ",x,101) == 0); - test_cond("sdsMakeRoomFor() final length",hi_sdslen(x)==101); + test_cond("sdsMakeRoomFor() final length",sdslen(x)==101); - hi_sdsfree(x); + sdsfree(x); } } - test_report(); + test_report() return 0; } #endif -#ifdef HI_SDS_TEST_MAIN +#ifdef SDS_TEST_MAIN int main(void) { - return hi_sdsTest(); + return sdsTest(); } #endif diff --git a/sds.h b/sds.h index 573d6dd19067..eda8833b598e 100644 --- a/sds.h +++ b/sds.h @@ -30,10 +30,10 @@ * POSSIBILITY OF SUCH DAMAGE. */ -#ifndef HIREDIS_SDS_H -#define HIREDIS_SDS_H +#ifndef __SDS_H +#define __SDS_H -#define HI_SDS_MAX_PREALLOC (1024*1024) +#define SDS_MAX_PREALLOC (1024*1024) #ifdef _MSC_VER #define __attribute__(x) typedef long long ssize_t; @@ -44,235 +44,235 @@ typedef long long ssize_t; #include #include -typedef char *hisds; +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__)) hisdshdr5 { +struct __attribute__ ((__packed__)) sdshdr5 { unsigned char flags; /* 3 lsb of type, and 5 msb of string length */ char buf[]; }; -struct __attribute__ ((__packed__)) hisdshdr8 { +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__)) hisdshdr16 { +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__)) hisdshdr32 { +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__)) hisdshdr64 { +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 HI_SDS_TYPE_5 0 -#define HI_SDS_TYPE_8 1 -#define HI_SDS_TYPE_16 2 -#define HI_SDS_TYPE_32 3 -#define HI_SDS_TYPE_64 4 -#define HI_SDS_TYPE_MASK 7 -#define HI_SDS_TYPE_BITS 3 -#define HI_SDS_HDR_VAR(T,s) struct hisdshdr##T *sh = (struct hisdshdr##T *)((s)-(sizeof(struct hisdshdr##T))); -#define HI_SDS_HDR(T,s) ((struct hisdshdr##T *)((s)-(sizeof(struct hisdshdr##T)))) -#define HI_SDS_TYPE_5_LEN(f) ((f)>>HI_SDS_TYPE_BITS) +#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 hi_sdslen(const hisds s) { +static inline size_t sdslen(const sds s) { unsigned char flags = s[-1]; - switch(flags & HI_SDS_TYPE_MASK) { - case HI_SDS_TYPE_5: - return HI_SDS_TYPE_5_LEN(flags); - case HI_SDS_TYPE_8: - return HI_SDS_HDR(8,s)->len; - case HI_SDS_TYPE_16: - return HI_SDS_HDR(16,s)->len; - case HI_SDS_TYPE_32: - return HI_SDS_HDR(32,s)->len; - case HI_SDS_TYPE_64: - return HI_SDS_HDR(64,s)->len; + 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 hi_sdsavail(const hisds s) { +static inline size_t sdsavail(const sds s) { unsigned char flags = s[-1]; - switch(flags&HI_SDS_TYPE_MASK) { - case HI_SDS_TYPE_5: { + switch(flags&SDS_TYPE_MASK) { + case SDS_TYPE_5: { return 0; } - case HI_SDS_TYPE_8: { - HI_SDS_HDR_VAR(8,s); + case SDS_TYPE_8: { + SDS_HDR_VAR(8,s); return sh->alloc - sh->len; } - case HI_SDS_TYPE_16: { - HI_SDS_HDR_VAR(16,s); + case SDS_TYPE_16: { + SDS_HDR_VAR(16,s); return sh->alloc - sh->len; } - case HI_SDS_TYPE_32: { - HI_SDS_HDR_VAR(32,s); + case SDS_TYPE_32: { + SDS_HDR_VAR(32,s); return sh->alloc - sh->len; } - case HI_SDS_TYPE_64: { - HI_SDS_HDR_VAR(64,s); + case SDS_TYPE_64: { + SDS_HDR_VAR(64,s); return sh->alloc - sh->len; } } return 0; } -static inline void hi_sdssetlen(hisds s, size_t newlen) { +static inline void sdssetlen(sds s, size_t newlen) { unsigned char flags = s[-1]; - switch(flags&HI_SDS_TYPE_MASK) { - case HI_SDS_TYPE_5: + switch(flags&SDS_TYPE_MASK) { + case SDS_TYPE_5: { unsigned char *fp = ((unsigned char*)s)-1; - *fp = (unsigned char)(HI_SDS_TYPE_5 | (newlen << HI_SDS_TYPE_BITS)); + *fp = (unsigned char)(SDS_TYPE_5 | (newlen << SDS_TYPE_BITS)); } break; - case HI_SDS_TYPE_8: - HI_SDS_HDR(8,s)->len = (uint8_t)newlen; + case SDS_TYPE_8: + SDS_HDR(8,s)->len = (uint8_t)newlen; break; - case HI_SDS_TYPE_16: - HI_SDS_HDR(16,s)->len = (uint16_t)newlen; + case SDS_TYPE_16: + SDS_HDR(16,s)->len = (uint16_t)newlen; break; - case HI_SDS_TYPE_32: - HI_SDS_HDR(32,s)->len = (uint32_t)newlen; + case SDS_TYPE_32: + SDS_HDR(32,s)->len = (uint32_t)newlen; break; - case HI_SDS_TYPE_64: - HI_SDS_HDR(64,s)->len = (uint64_t)newlen; + case SDS_TYPE_64: + SDS_HDR(64,s)->len = (uint64_t)newlen; break; } } -static inline void hi_sdsinclen(hisds s, size_t inc) { +static inline void sdsinclen(sds s, size_t inc) { unsigned char flags = s[-1]; - switch(flags&HI_SDS_TYPE_MASK) { - case HI_SDS_TYPE_5: + switch(flags&SDS_TYPE_MASK) { + case SDS_TYPE_5: { unsigned char *fp = ((unsigned char*)s)-1; - unsigned char newlen = HI_SDS_TYPE_5_LEN(flags)+(unsigned char)inc; - *fp = HI_SDS_TYPE_5 | (newlen << HI_SDS_TYPE_BITS); + unsigned char newlen = SDS_TYPE_5_LEN(flags)+(unsigned char)inc; + *fp = SDS_TYPE_5 | (newlen << SDS_TYPE_BITS); } break; - case HI_SDS_TYPE_8: - HI_SDS_HDR(8,s)->len += (uint8_t)inc; + case SDS_TYPE_8: + SDS_HDR(8,s)->len += (uint8_t)inc; break; - case HI_SDS_TYPE_16: - HI_SDS_HDR(16,s)->len += (uint16_t)inc; + case SDS_TYPE_16: + SDS_HDR(16,s)->len += (uint16_t)inc; break; - case HI_SDS_TYPE_32: - HI_SDS_HDR(32,s)->len += (uint32_t)inc; + case SDS_TYPE_32: + SDS_HDR(32,s)->len += (uint32_t)inc; break; - case HI_SDS_TYPE_64: - HI_SDS_HDR(64,s)->len += (uint64_t)inc; + case SDS_TYPE_64: + SDS_HDR(64,s)->len += (uint64_t)inc; break; } } -/* hi_sdsalloc() = hi_sdsavail() + hi_sdslen() */ -static inline size_t hi_sdsalloc(const hisds s) { +/* sdsalloc() = sdsavail() + sdslen() */ +static inline size_t sdsalloc(const sds s) { unsigned char flags = s[-1]; - switch(flags & HI_SDS_TYPE_MASK) { - case HI_SDS_TYPE_5: - return HI_SDS_TYPE_5_LEN(flags); - case HI_SDS_TYPE_8: - return HI_SDS_HDR(8,s)->alloc; - case HI_SDS_TYPE_16: - return HI_SDS_HDR(16,s)->alloc; - case HI_SDS_TYPE_32: - return HI_SDS_HDR(32,s)->alloc; - case HI_SDS_TYPE_64: - return HI_SDS_HDR(64,s)->alloc; + 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 hi_sdssetalloc(hisds s, size_t newlen) { +static inline void sdssetalloc(sds s, size_t newlen) { unsigned char flags = s[-1]; - switch(flags&HI_SDS_TYPE_MASK) { - case HI_SDS_TYPE_5: + switch(flags&SDS_TYPE_MASK) { + case SDS_TYPE_5: /* Nothing to do, this type has no total allocation info. */ break; - case HI_SDS_TYPE_8: - HI_SDS_HDR(8,s)->alloc = (uint8_t)newlen; + case SDS_TYPE_8: + SDS_HDR(8,s)->alloc = (uint8_t)newlen; break; - case HI_SDS_TYPE_16: - HI_SDS_HDR(16,s)->alloc = (uint16_t)newlen; + case SDS_TYPE_16: + SDS_HDR(16,s)->alloc = (uint16_t)newlen; break; - case HI_SDS_TYPE_32: - HI_SDS_HDR(32,s)->alloc = (uint32_t)newlen; + case SDS_TYPE_32: + SDS_HDR(32,s)->alloc = (uint32_t)newlen; break; - case HI_SDS_TYPE_64: - HI_SDS_HDR(64,s)->alloc = (uint64_t)newlen; + case SDS_TYPE_64: + SDS_HDR(64,s)->alloc = (uint64_t)newlen; break; } } -hisds hi_sdsnewlen(const void *init, size_t initlen); -hisds hi_sdsnew(const char *init); -hisds hi_sdsempty(void); -hisds hi_sdsdup(const hisds s); -void hi_sdsfree(hisds s); -hisds hi_sdsgrowzero(hisds s, size_t len); -hisds hi_sdscatlen(hisds s, const void *t, size_t len); -hisds hi_sdscat(hisds s, const char *t); -hisds hi_sdscatsds(hisds s, const hisds t); -hisds hi_sdscpylen(hisds s, const char *t, size_t len); -hisds hi_sdscpy(hisds s, const char *t); +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); -hisds hi_sdscatvprintf(hisds s, const char *fmt, va_list ap); +sds sdscatvprintf(sds s, const char *fmt, va_list ap); #ifdef __GNUC__ -hisds hi_sdscatprintf(hisds s, const char *fmt, ...) +sds sdscatprintf(sds s, const char *fmt, ...) __attribute__((format(printf, 2, 3))); #else -hisds hi_sdscatprintf(hisds s, const char *fmt, ...); +sds sdscatprintf(sds s, const char *fmt, ...); #endif -hisds hi_sdscatfmt(hisds s, char const *fmt, ...); -hisds hi_sdstrim(hisds s, const char *cset); -int hi_sdsrange(hisds s, ssize_t start, ssize_t end); -void hi_sdsupdatelen(hisds s); -void hi_sdsclear(hisds s); -int hi_sdscmp(const hisds s1, const hisds s2); -hisds *hi_sdssplitlen(const char *s, int len, const char *sep, int seplen, int *count); -void hi_sdsfreesplitres(hisds *tokens, int count); -void hi_sdstolower(hisds s); -void hi_sdstoupper(hisds s); -hisds hi_sdsfromlonglong(long long value); -hisds hi_sdscatrepr(hisds s, const char *p, size_t len); -hisds *hi_sdssplitargs(const char *line, int *argc); -hisds hi_sdsmapchars(hisds s, const char *from, const char *to, size_t setlen); -hisds hi_sdsjoin(char **argv, int argc, char *sep); -hisds hi_sdsjoinsds(hisds *argv, int argc, const char *sep, size_t seplen); +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 */ -hisds hi_sdsMakeRoomFor(hisds s, size_t addlen); -void hi_sdsIncrLen(hisds s, int incr); -hisds hi_sdsRemoveFreeSpace(hisds s); -size_t hi_sdsAllocSize(hisds s); -void *hi_sdsAllocPtr(hisds s); +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 *hi_sds_malloc(size_t size); -void *hi_sds_realloc(void *ptr, size_t size); -void hi_sds_free(void *ptr); +void *sds_malloc(size_t size); +void *sds_realloc(void *ptr, size_t size); +void sds_free(void *ptr); #ifdef REDIS_TEST -int hi_sdsTest(int argc, char *argv[]); +int sdsTest(int argc, char *argv[]); #endif -#endif /* HIREDIS_SDS_H */ +#endif diff --git a/sdsalloc.h b/sdsalloc.h index c9dcc3df8e4b..5538dd94c973 100644 --- a/sdsalloc.h +++ b/sdsalloc.h @@ -39,6 +39,6 @@ #include "alloc.h" -#define hi_s_malloc hi_malloc -#define hi_s_realloc hi_realloc -#define hi_s_free hi_free +#define s_malloc hi_malloc +#define s_realloc hi_realloc +#define s_free hi_free diff --git a/sdscompat.h b/sdscompat.h deleted file mode 100644 index e5a2574f31e5..000000000000 --- a/sdscompat.h +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (c) 2020, Michael Grunder - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * * 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. - * * Neither the name of Redis 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 OWNER 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. - */ - -/* - * SDS compatibility header. - * - * This simple file maps sds types and calls to their unique hiredis symbol names. - * It's useful when we build Hiredis as a dependency of Redis and want to call - * Hiredis' sds symbols rather than the ones built into Redis, as the libraries - * have slightly diverged and could cause hard to track down ABI incompatibility - * bugs. - * - */ - -#ifndef HIREDIS_SDS_COMPAT -#define HIREDIS_SDS_COMPAT - -#define sds hisds - -#define sdslen hi_sdslen -#define sdsavail hi_sdsavail -#define sdssetlen hi_sdssetlen -#define sdsinclen hi_sdsinclen -#define sdsalloc hi_sdsalloc -#define sdssetalloc hi_sdssetalloc - -#define sdsAllocPtr hi_sdsAllocPtr -#define sdsAllocSize hi_sdsAllocSize -#define sdscat hi_sdscat -#define sdscatfmt hi_sdscatfmt -#define sdscatlen hi_sdscatlen -#define sdscatprintf hi_sdscatprintf -#define sdscatrepr hi_sdscatrepr -#define sdscatsds hi_sdscatsds -#define sdscatvprintf hi_sdscatvprintf -#define sdsclear hi_sdsclear -#define sdscmp hi_sdscmp -#define sdscpy hi_sdscpy -#define sdscpylen hi_sdscpylen -#define sdsdup hi_sdsdup -#define sdsempty hi_sdsempty -#define sds_free hi_sds_free -#define sdsfree hi_sdsfree -#define sdsfreesplitres hi_sdsfreesplitres -#define sdsfromlonglong hi_sdsfromlonglong -#define sdsgrowzero hi_sdsgrowzero -#define sdsIncrLen hi_sdsIncrLen -#define sdsjoin hi_sdsjoin -#define sdsjoinsds hi_sdsjoinsds -#define sdsll2str hi_sdsll2str -#define sdsMakeRoomFor hi_sdsMakeRoomFor -#define sds_malloc hi_sds_malloc -#define sdsmapchars hi_sdsmapchars -#define sdsnew hi_sdsnew -#define sdsnewlen hi_sdsnewlen -#define sdsrange hi_sdsrange -#define sds_realloc hi_sds_realloc -#define sdsRemoveFreeSpace hi_sdsRemoveFreeSpace -#define sdssplitargs hi_sdssplitargs -#define sdssplitlen hi_sdssplitlen -#define sdstolower hi_sdstolower -#define sdstoupper hi_sdstoupper -#define sdstrim hi_sdstrim -#define sdsull2str hi_sdsull2str -#define sdsupdatelen hi_sdsupdatelen - -#endif /* HIREDIS_SDS_COMPAT */ diff --git a/ssl.c b/ssl.c index fe9a2fdce9a1..c581f63dc109 100644 --- a/ssl.c +++ b/ssl.c @@ -38,6 +38,7 @@ #include #ifdef _WIN32 #include +#include #else #include #endif @@ -182,6 +183,10 @@ const char *redisSSLContextGetError(redisSSLContextError error) return "Failed to load client certificate"; case REDIS_SSL_CTX_PRIVATE_KEY_LOAD_FAILED: return "Failed to load private key"; + case REDIS_SSL_CTX_OS_CERTSTORE_OPEN_FAILED: + return "Failed to open system certifcate store"; + case REDIS_SSL_CTX_OS_CERT_ADD_FAILED: + return "Failed to add CA certificates obtained from system to the SSL context"; default: return "Unknown error code"; } @@ -214,6 +219,11 @@ redisSSLContext *redisCreateSSLContext(const char *cacert_filename, const char * const char *cert_filename, const char *private_key_filename, const char *server_name, redisSSLContextError *error) { +#ifdef _WIN32 + HCERTSTORE win_store = NULL; + PCCERT_CONTEXT win_ctx = NULL; +#endif + redisSSLContext *ctx = hi_calloc(1, sizeof(redisSSLContext)); if (ctx == NULL) goto error; @@ -234,6 +244,31 @@ redisSSLContext *redisCreateSSLContext(const char *cacert_filename, const char * } if (capath || cacert_filename) { +#ifdef _WIN32 + if (0 == strcmp(cacert_filename, "wincert")) { + win_store = CertOpenSystemStore(NULL, "Root"); + if (!win_store) { + if (error) *error = REDIS_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 = REDIS_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 = REDIS_SSL_CTX_CA_CERT_LOAD_FAILED; goto error; @@ -257,6 +292,10 @@ redisSSLContext *redisCreateSSLContext(const char *cacert_filename, const char * return ctx; error: +#ifdef _WIN32 + CertFreeCertificateContext(win_ctx); + CertCloseStore(win_store, 0); +#endif redisFreeSSLContext(ctx); return NULL; } @@ -353,7 +392,11 @@ int redisInitiateSSLWithContext(redisContext *c, redisSSLContext *redis_ssl_ctx) } } - return redisSSLConnect(c, ssl); + if (redisSSLConnect(c, ssl) != REDIS_OK) { + goto error; + } + + return REDIS_OK; error: if (ssl) @@ -437,7 +480,7 @@ static ssize_t redisSSLRead(redisContext *c, char *buf, size_t bufcap) { static ssize_t redisSSLWrite(redisContext *c) { redisSSL *rssl = c->privctx; - size_t len = rssl->lastLen ? rssl->lastLen : hi_sdslen(c->obuf); + size_t len = rssl->lastLen ? rssl->lastLen : sdslen(c->obuf); int rv = SSL_write(rssl->ssl, c->obuf, len); if (rv > 0) { diff --git a/test.c b/test.c index 012b6ad61a3b..f991ef1e75dc 100644 --- a/test.c +++ b/test.c @@ -11,12 +11,17 @@ #include #include #include +#include #include "hiredis.h" #include "async.h" #ifdef HIREDIS_TEST_SSL #include "hiredis_ssl.h" #endif +#ifdef HIREDIS_TEST_ASYNC +#include "adapters/libevent.h" +#include +#endif #include "net.h" #include "win32.h" @@ -53,6 +58,13 @@ struct privdata { int dtor_counter; }; +struct pushCounters { + int nil; + int str; +}; + +static int insecure_calloc_calls; + #ifdef HIREDIS_TEST_SSL redisSSLContext *_ssl_ctx = NULL; #endif @@ -340,21 +352,21 @@ static void test_format_commands(void) { len == 4+4+(3+2)+4+(7+2)+4+(3+2)); hi_free(cmd); - hisds sds_cmd; + sds sds_cmd; sds_cmd = NULL; - test("Format command into hisds by passing argc/argv without lengths: "); + test("Format command into sds by passing argc/argv without lengths: "); len = redisFormatSdsCommandArgv(&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)); - hi_sdsfree(sds_cmd); + sdsfree(sds_cmd); sds_cmd = NULL; - test("Format command into hisds by passing argc/argv with lengths: "); + test("Format command into sds by passing argc/argv with lengths: "); len = redisFormatSdsCommandArgv(&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)); - hi_sdsfree(sds_cmd); + sdsfree(sds_cmd); } static void test_append_formatted_commands(struct config config) { @@ -493,6 +505,20 @@ static void test_reply_reader(void) { freeReplyObject(reply); redisReaderFree(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 = redisReaderCreate(); + reader->maxelements = 0; /* Don't rely on default limit */ + redisReaderFeed(reader, bad_mbulk_reply, strlen(bad_mbulk_reply)); + ret = redisReaderGetReply(reader,&reply); + test_cond(ret == REDIS_ERR && strcasecmp(reader->errstr, "Out of memory") == 0); + freeReplyObject(reply); + redisReaderFree(reader); + #if LLONG_MAX > SIZE_MAX test("Set error when array > SIZE_MAX: "); reader = redisReaderCreate(); @@ -578,6 +604,147 @@ static void test_reply_reader(void) { ((redisReply*)reply)->element[1]->integer == 42); freeReplyObject(reply); redisReaderFree(reader); + + test("Can parse RESP3 doubles: "); + reader = redisReaderCreate(); + redisReaderFeed(reader, ",3.14159265358979323846\r\n",25); + ret = redisReaderGetReply(reader,&reply); + test_cond(ret == REDIS_OK && + ((redisReply*)reply)->type == REDIS_REPLY_DOUBLE && + fabs(((redisReply*)reply)->dval - 3.14159265358979323846) < 0.00000001 && + ((redisReply*)reply)->len == 22 && + strcmp(((redisReply*)reply)->str, "3.14159265358979323846") == 0); + freeReplyObject(reply); + redisReaderFree(reader); + + test("Set error on invalid RESP3 double: "); + reader = redisReaderCreate(); + redisReaderFeed(reader, ",3.14159\000265358979323846\r\n",26); + ret = redisReaderGetReply(reader,&reply); + test_cond(ret == REDIS_ERR && + strcasecmp(reader->errstr,"Bad double value") == 0); + freeReplyObject(reply); + redisReaderFree(reader); + + test("Correctly parses RESP3 double INFINITY: "); + reader = redisReaderCreate(); + redisReaderFeed(reader, ",inf\r\n",6); + ret = redisReaderGetReply(reader,&reply); + test_cond(ret == REDIS_OK && + ((redisReply*)reply)->type == REDIS_REPLY_DOUBLE && + isinf(((redisReply*)reply)->dval) && + ((redisReply*)reply)->dval > 0); + freeReplyObject(reply); + redisReaderFree(reader); + + test("Set error when RESP3 double is NaN: "); + reader = redisReaderCreate(); + redisReaderFeed(reader, ",nan\r\n",6); + ret = redisReaderGetReply(reader,&reply); + test_cond(ret == REDIS_ERR && + strcasecmp(reader->errstr,"Bad double value") == 0); + freeReplyObject(reply); + redisReaderFree(reader); + + test("Can parse RESP3 nil: "); + reader = redisReaderCreate(); + redisReaderFeed(reader, "_\r\n",3); + ret = redisReaderGetReply(reader,&reply); + test_cond(ret == REDIS_OK && + ((redisReply*)reply)->type == REDIS_REPLY_NIL); + freeReplyObject(reply); + redisReaderFree(reader); + + test("Set error on invalid RESP3 nil: "); + reader = redisReaderCreate(); + redisReaderFeed(reader, "_nil\r\n",6); + ret = redisReaderGetReply(reader,&reply); + test_cond(ret == REDIS_ERR && + strcasecmp(reader->errstr,"Bad nil value") == 0); + freeReplyObject(reply); + redisReaderFree(reader); + + test("Can parse RESP3 bool (true): "); + reader = redisReaderCreate(); + redisReaderFeed(reader, "#t\r\n",4); + ret = redisReaderGetReply(reader,&reply); + test_cond(ret == REDIS_OK && + ((redisReply*)reply)->type == REDIS_REPLY_BOOL && + ((redisReply*)reply)->integer); + freeReplyObject(reply); + redisReaderFree(reader); + + test("Can parse RESP3 bool (false): "); + reader = redisReaderCreate(); + redisReaderFeed(reader, "#f\r\n",4); + ret = redisReaderGetReply(reader,&reply); + test_cond(ret == REDIS_OK && + ((redisReply*)reply)->type == REDIS_REPLY_BOOL && + !((redisReply*)reply)->integer); + freeReplyObject(reply); + redisReaderFree(reader); + + test("Set error on invalid RESP3 bool: "); + reader = redisReaderCreate(); + redisReaderFeed(reader, "#foobar\r\n",9); + ret = redisReaderGetReply(reader,&reply); + test_cond(ret == REDIS_ERR && + strcasecmp(reader->errstr,"Bad bool value") == 0); + freeReplyObject(reply); + redisReaderFree(reader); + + test("Can parse RESP3 map: "); + reader = redisReaderCreate(); + redisReaderFeed(reader, "%2\r\n+first\r\n:123\r\n$6\r\nsecond\r\n#t\r\n",34); + ret = redisReaderGetReply(reader,&reply); + test_cond(ret == REDIS_OK && + ((redisReply*)reply)->type == REDIS_REPLY_MAP && + ((redisReply*)reply)->elements == 4 && + ((redisReply*)reply)->element[0]->type == REDIS_REPLY_STATUS && + ((redisReply*)reply)->element[0]->len == 5 && + !strcmp(((redisReply*)reply)->element[0]->str,"first") && + ((redisReply*)reply)->element[1]->type == REDIS_REPLY_INTEGER && + ((redisReply*)reply)->element[1]->integer == 123 && + ((redisReply*)reply)->element[2]->type == REDIS_REPLY_STRING && + ((redisReply*)reply)->element[2]->len == 6 && + !strcmp(((redisReply*)reply)->element[2]->str,"second") && + ((redisReply*)reply)->element[3]->type == REDIS_REPLY_BOOL && + ((redisReply*)reply)->element[3]->integer); + freeReplyObject(reply); + redisReaderFree(reader); + + test("Can parse RESP3 set: "); + reader = redisReaderCreate(); + redisReaderFeed(reader, "~5\r\n+orange\r\n$5\r\napple\r\n#f\r\n:100\r\n:999\r\n",40); + ret = redisReaderGetReply(reader,&reply); + test_cond(ret == REDIS_OK && + ((redisReply*)reply)->type == REDIS_REPLY_SET && + ((redisReply*)reply)->elements == 5 && + ((redisReply*)reply)->element[0]->type == REDIS_REPLY_STATUS && + ((redisReply*)reply)->element[0]->len == 6 && + !strcmp(((redisReply*)reply)->element[0]->str,"orange") && + ((redisReply*)reply)->element[1]->type == REDIS_REPLY_STRING && + ((redisReply*)reply)->element[1]->len == 5 && + !strcmp(((redisReply*)reply)->element[1]->str,"apple") && + ((redisReply*)reply)->element[2]->type == REDIS_REPLY_BOOL && + !((redisReply*)reply)->element[2]->integer && + ((redisReply*)reply)->element[3]->type == REDIS_REPLY_INTEGER && + ((redisReply*)reply)->element[3]->integer == 100 && + ((redisReply*)reply)->element[4]->type == REDIS_REPLY_INTEGER && + ((redisReply*)reply)->element[4]->integer == 999); + freeReplyObject(reply); + redisReaderFree(reader); + + test("Can parse RESP3 bignum: "); + reader = redisReaderCreate(); + redisReaderFeed(reader,"(3492890328409238509324850943850943825024385\r\n",46); + ret = redisReaderGetReply(reader,&reply); + test_cond(ret == REDIS_OK && + ((redisReply*)reply)->type == REDIS_REPLY_BIGNUM && + ((redisReply*)reply)->len == 43 && + !strcmp(((redisReply*)reply)->str,"3492890328409238509324850943850943825024385")); + freeReplyObject(reply); + redisReaderFree(reader); } static void test_free_null(void) { @@ -604,6 +771,13 @@ static void *hi_calloc_fail(size_t nmemb, size_t 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; @@ -611,6 +785,8 @@ static void *hi_realloc_fail(void *ptr, size_t size) { } static void test_allocator_injection(void) { + void *ptr; + hiredisAllocFuncs ha = { .mallocFn = hi_malloc_fail, .callocFn = hi_calloc_fail, @@ -630,6 +806,13 @@ static void test_allocator_injection(void) { redisReader *reader = redisReaderCreate(); test_cond(reader == NULL); + /* Make sure hiredis itself protects against a non-overflow checking calloc */ + test("hiredis calloc wrapper protects against overflow: "); + ha.callocFn = hi_calloc_insecure; + hiredisSetAllocators(&ha); + ptr = hi_calloc((SIZE_MAX / sizeof(void*)) + 3, sizeof(void*)); + test_cond(ptr == NULL && insecure_calloc_calls == 0); + // Return allocators to default hiredisResetAllocators(); } @@ -677,11 +860,25 @@ static void test_blocking_connection_errors(void) { #endif } -/* Dummy push handler */ -void push_handler(void *privdata, void *reply) { - int *counter = privdata; +/* Test push handler */ +void push_handler(void *privdata, void *r) { + struct pushCounters *pcounts = privdata; + redisReply *reply = r, *payload; + + assert(reply && reply->type == REDIS_REPLY_PUSH && reply->elements == 2); + + payload = reply->element[1]; + if (payload->type == REDIS_REPLY_ARRAY) { + payload = payload->element[0]; + } + + if (payload->type == REDIS_REPLY_STRING) { + pcounts->str++; + } else if (payload->type == REDIS_REPLY_NIL) { + pcounts->nil++; + } + freeReplyObject(reply); - *counter += 1; } /* Dummy function just to test setting a callback with redisOptions */ @@ -691,16 +888,16 @@ void push_handler_async(redisAsyncContext *ac, void *reply) { } static void test_resp3_push_handler(redisContext *c) { + struct pushCounters pc = {0}; redisPushFn *old = NULL; redisReply *reply; void *privdata; - int n = 0; /* Switch to RESP3 and turn on client tracking */ send_hello(c, 3); send_client_tracking(c, "ON"); privdata = c->privdata; - c->privdata = &n; + c->privdata = &pc; reply = redisCommand(c, "GET key:0"); assert(reply != NULL); @@ -717,7 +914,12 @@ static void test_resp3_push_handler(redisContext *c) { old = redisSetPushCallback(c, push_handler); test("We can set a custom RESP3 PUSH handler: "); reply = redisCommand(c, "SET key:0 val:0"); - test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS && n == 1); + test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS && pc.str == 1); + freeReplyObject(reply); + + test("We properly handle a NIL invalidation payload: "); + reply = redisCommand(c, "FLUSHDB"); + test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS && pc.nil == 1); freeReplyObject(reply); /* Unset the push callback and generate an invalidate message making @@ -1245,6 +1447,440 @@ static void test_throughput(struct config config) { // redisFree(c); // } +#ifdef HIREDIS_TEST_ASYNC +struct event_base *base; + +typedef struct TestState { + redisOptions *options; + int checkpoint; + int resp3; + int disconnect; +} TestState; + +/* Helper to disconnect and stop event loop */ +void async_disconnect(redisAsyncContext *ac) { + redisAsyncDisconnect(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(redisAsyncContext *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(redisOptions *options, const char* channel, const char* msg) { + redisContext *c = redisConnectWithOptions(options); + assert(c != NULL); + redisReply *reply = redisCommand(c,"PUBLISH %s %s",channel,msg); + assert(reply->type == REDIS_REPLY_INTEGER && reply->integer == 1); + freeReplyObject(reply); + disconnect(c, 0); +} + +/* Expect a reply of type INTEGER */ +void integer_cb(redisAsyncContext *ac, void *r, void *privdata) { + redisReply *reply = r; + TestState *state = privdata; + assert(reply != NULL && reply->type == REDIS_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(redisAsyncContext *ac, void *r, void *privdata) { + redisReply *reply = r; + TestState *state = privdata; + + assert(reply != NULL && + reply->type == (state->resp3 ? REDIS_REPLY_PUSH : REDIS_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 */ + redisAsyncCommand(ac,unexpected_cb, + (void*)"unsubscribe should call subscribe_cb()", + "unsubscribe"); + /* Send a regular command after unsubscribing, then disconnect */ + state->disconnect = 1; + redisAsyncCommand(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(redisAsyncContext *ac, void *r, void *privdata) { + redisReply *reply = r; + TestState *state = privdata; + assert(reply != NULL && reply->type == REDIS_REPLY_ARRAY); + state->checkpoint++; + if (state->disconnect) async_disconnect(ac); +} + +/* Expect a NULL reply */ +void null_cb(redisAsyncContext *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 */ + redisOptions options = get_redis_tcp_options(config); + redisAsyncContext *ac = redisAsyncConnectWithOptions(&options); + assert(ac != NULL && ac->err == 0); + redisLibeventAttach(ac,base); + + /* Start subscribe */ + TestState state = {.options = &options}; + redisAsyncCommand(ac,subscribe_cb,&state,"subscribe mychannel"); + + /* Make sure non-subscribe commands are handled */ + redisAsyncCommand(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(redisAsyncContext *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 */ + redisOptions options = get_redis_tcp_options(config); + redisAsyncContext *ac = redisAsyncConnectWithOptions(&options); + assert(ac != NULL && ac->err == 0); + redisLibeventAttach(ac,base); + + /* Not expecting any push messages in this test */ + redisAsyncSetPushCallback(ac, unexpected_push_cb); + + /* Switch protocol */ + redisAsyncCommand(ac,NULL,NULL,"HELLO 3"); + + /* Start subscribe */ + TestState state = {.options = &options, .resp3 = 1}; + redisAsyncCommand(ac,subscribe_cb,&state,"subscribe mychannel"); + + /* Make sure non-subscribe commands are handled in RESP3 */ + redisAsyncCommand(ac,integer_cb,&state,"LPUSH mylist foo"); + redisAsyncCommand(ac,integer_cb,&state,"LPUSH mylist foo"); + redisAsyncCommand(ac,integer_cb,&state,"LPUSH mylist foo"); + /* Handle an array with 3 elements as a non-subscribe command */ + redisAsyncCommand(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(redisAsyncContext *ac, void *r, void *privdata) { + redisReply *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 ? REDIS_REPLY_PUSH : REDIS_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 */ + redisAsyncCommand(ac,null_cb,state,"DEBUG SLEEP 3"); + redisAsyncCommand(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 */ + redisOptions options = get_redis_tcp_options(config); + redisAsyncContext *ac = redisAsyncConnectWithOptions(&options); + assert(ac != NULL && ac->err == 0); + redisLibeventAttach(ac,base); + + /* Configure a command timout */ + struct timeval command_timeout = {.tv_sec = 2}; + redisAsyncSetTimeout(ac,command_timeout); + + /* Not expecting any push messages in this test */ + redisAsyncSetPushCallback(ac,unexpected_push_cb); + + /* Switch protocol */ + redisAsyncCommand(ac,NULL,NULL,"HELLO 3"); + + /* Start subscribe */ + TestState state = {.options = &options, .resp3 = 1}; + redisAsyncCommand(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(redisAsyncContext *ac, void *r, void *privdata) { + redisReply *reply = r; + TestState *state = privdata; + + assert(reply != NULL && reply->type == REDIS_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 a channel X which we don't subscribe to */ + redisAsyncCommand(ac,unexpected_cb, + (void*)"unsubscribe should not call unexpected_cb()", + "unsubscribe B X A"); + /* Send a regular command after unsubscribing, then disconnect */ + state->disconnect = 1; + redisAsyncCommand(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(redisAsyncContext *ac, void *r, void *privdata) { + redisReply *reply = r; + TestState *state = privdata; + + assert(reply != NULL && reply->type == REDIS_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 and A + * where channel X is not subscribed to. + * - 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 */ + redisOptions options = get_redis_tcp_options(config); + redisAsyncContext *ac = redisAsyncConnectWithOptions(&options); + assert(ac != NULL && ac->err == 0); + redisLibeventAttach(ac,base); + + /* Not expecting any push messages in this test */ + redisAsyncSetPushCallback(ac,unexpected_push_cb); + + /* Start subscribing to two channels */ + TestState state = {.options = &options}; + redisAsyncCommand(ac,subscribe_channel_a_cb,&state,"subscribe A"); + redisAsyncCommand(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(redisAsyncContext *ac, void *r, void *privdata) { + redisReply *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 == REDIS_REPLY_STATUS); + state->checkpoint++; + + if (state->checkpoint == 1) { + /* Response from MONITOR */ + redisContext *c = redisConnectWithOptions(state->options); + assert(c != NULL); + redisReply *reply = redisCommand(c,"SET first 1"); + assert(reply->type == REDIS_REPLY_STATUS); + freeReplyObject(reply); + redisFree(c); + } else if (state->checkpoint == 2) { + /* Response for monitored command 'SET first 1' */ + assert(strstr(reply->str,"first") != NULL); + redisContext *c = redisConnectWithOptions(state->options); + assert(c != NULL); + redisReply *reply = redisCommand(c,"SET second 2"); + assert(reply->type == REDIS_REPLY_STATUS); + freeReplyObject(reply); + redisFree(c); + } else if (state->checkpoint == 3) { + /* Response for monitored command 'SET second 2' */ + assert(strstr(reply->str,"second") != NULL); + /* Send QUIT to disconnect */ + redisAsyncCommand(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 */ + redisOptions options = get_redis_tcp_options(config); + redisAsyncContext *ac = redisAsyncConnectWithOptions(&options); + assert(ac != NULL && ac->err == 0); + redisLibeventAttach(ac,base); + + /* Not expecting any push messages in this test */ + redisAsyncSetPushCallback(ac,unexpected_push_cb); + + /* Start monitor */ + TestState state = {.options = &options}; + redisAsyncCommand(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 /* HIREDIS_TEST_ASYNC */ + int main(int argc, char **argv) { struct config cfg = { .tcp = { @@ -1363,6 +1999,24 @@ int main(int argc, char **argv) { } #endif +#ifdef HIREDIS_TEST_ASYNC + printf("\nTesting asynchronous API against TCP connection (%s:%d):\n", cfg.tcp.host, cfg.tcp.port); + cfg.type = CONN_TCP; + + int major; + redisContext *c = do_connect(cfg); + get_redis_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 /* HIREDIS_TEST_ASYNC */ + if (test_inherit_fd) { printf("\nTesting against inherited fd (%s): ", cfg.unix_sock.path); if (test_unix_socket) {