summaryrefslogtreecommitdiffstats
path: root/deps/hiredis
diff options
context:
space:
mode:
Diffstat (limited to 'deps/hiredis')
-rw-r--r--deps/hiredis/.github/workflows/build.yml205
-rw-r--r--deps/hiredis/.gitignore9
-rw-r--r--deps/hiredis/.travis.yml125
-rw-r--r--deps/hiredis/CHANGELOG.md364
-rw-r--r--deps/hiredis/CMakeLists.txt245
-rw-r--r--deps/hiredis/COPYING29
-rw-r--r--deps/hiredis/Makefile338
-rw-r--r--deps/hiredis/README.md671
-rw-r--r--deps/hiredis/adapters/ae.h130
-rw-r--r--deps/hiredis/adapters/glib.h156
-rw-r--r--deps/hiredis/adapters/ivykis.h84
-rw-r--r--deps/hiredis/adapters/libev.h188
-rw-r--r--deps/hiredis/adapters/libevent.h175
-rw-r--r--deps/hiredis/adapters/libuv.h163
-rw-r--r--deps/hiredis/adapters/macosx.h115
-rw-r--r--deps/hiredis/adapters/qt.h135
-rw-r--r--deps/hiredis/alloc.c90
-rw-r--r--deps/hiredis/alloc.h96
-rw-r--r--deps/hiredis/appveyor.yml24
-rw-r--r--deps/hiredis/async.c909
-rw-r--r--deps/hiredis/async.h147
-rw-r--r--deps/hiredis/async_private.h75
-rw-r--r--deps/hiredis/dict.c343
-rw-r--r--deps/hiredis/dict.h125
-rw-r--r--deps/hiredis/examples/CMakeLists.txt49
-rw-r--r--deps/hiredis/examples/example-ae.c62
-rw-r--r--deps/hiredis/examples/example-glib.c73
-rw-r--r--deps/hiredis/examples/example-ivykis.c60
-rw-r--r--deps/hiredis/examples/example-libev.c54
-rw-r--r--deps/hiredis/examples/example-libevent-ssl.c90
-rw-r--r--deps/hiredis/examples/example-libevent.c67
-rw-r--r--deps/hiredis/examples/example-libuv.c81
-rw-r--r--deps/hiredis/examples/example-macosx.c66
-rw-r--r--deps/hiredis/examples/example-push.c159
-rw-r--r--deps/hiredis/examples/example-qt.cpp46
-rw-r--r--deps/hiredis/examples/example-qt.h32
-rw-r--r--deps/hiredis/examples/example-ssl.c113
-rw-r--r--deps/hiredis/examples/example.c94
-rw-r--r--deps/hiredis/fmacros.h14
-rw-r--r--deps/hiredis/fuzzing/format_command_fuzzer.c57
-rw-r--r--deps/hiredis/hiredis-config.cmake.in13
-rw-r--r--deps/hiredis/hiredis.c1188
-rw-r--r--deps/hiredis/hiredis.h348
-rw-r--r--deps/hiredis/hiredis.pc.in12
-rw-r--r--deps/hiredis/hiredis.targets11
-rw-r--r--deps/hiredis/hiredis_ssl-config.cmake.in13
-rw-r--r--deps/hiredis/hiredis_ssl.h129
-rw-r--r--deps/hiredis/hiredis_ssl.pc.in12
-rw-r--r--deps/hiredis/net.c612
-rw-r--r--deps/hiredis/net.h56
-rw-r--r--deps/hiredis/read.c785
-rw-r--r--deps/hiredis/read.h129
-rw-r--r--deps/hiredis/sds.c1289
-rw-r--r--deps/hiredis/sds.h278
-rw-r--r--deps/hiredis/sdsalloc.h44
-rw-r--r--deps/hiredis/sdscompat.h94
-rw-r--r--deps/hiredis/sockcompat.c248
-rw-r--r--deps/hiredis/sockcompat.h92
-rw-r--r--deps/hiredis/ssl.c569
-rw-r--r--deps/hiredis/test.c2041
-rwxr-xr-xdeps/hiredis/test.sh78
-rw-r--r--deps/hiredis/win32.h56
62 files changed, 14155 insertions, 0 deletions
diff --git a/deps/hiredis/.github/workflows/build.yml b/deps/hiredis/.github/workflows/build.yml
new file mode 100644
index 0000000..362bc77
--- /dev/null
+++ b/deps/hiredis/.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/deps/hiredis/.gitignore b/deps/hiredis/.gitignore
new file mode 100644
index 0000000..056959f
--- /dev/null
+++ b/deps/hiredis/.gitignore
@@ -0,0 +1,9 @@
+/hiredis-test
+/examples/hiredis-example*
+/*.o
+/*.so
+/*.dylib
+/*.a
+/*.pc
+*.dSYM
+tags
diff --git a/deps/hiredis/.travis.yml b/deps/hiredis/.travis.yml
new file mode 100644
index 0000000..1e9b556
--- /dev/null
+++ b/deps/hiredis/.travis.yml
@@ -0,0 +1,125 @@
+language: c
+compiler:
+ - gcc
+ - clang
+
+os:
+ - linux
+ - osx
+
+dist: bionic
+
+branches:
+ only:
+ - staging
+ - trying
+ - master
+ - /^release\/.*$/
+
+install:
+ - if [ "$TRAVIS_COMPILER" != "mingw" ]; then
+ wget https://github.com/redis/redis/archive/6.0.6.tar.gz;
+ tar -xzvf 6.0.6.tar.gz;
+ pushd redis-6.0.6 && BUILD_TLS=yes make && export PATH=$PWD/src:$PATH && popd;
+ fi;
+
+before_script:
+ - if [ "$TRAVIS_OS_NAME" == "osx" ]; then
+ curl -O https://distfiles.macports.org/MacPorts/MacPorts-2.6.2-10.13-HighSierra.pkg;
+ sudo installer -pkg MacPorts-2.6.2-10.13-HighSierra.pkg -target /;
+ export PATH=$PATH:/opt/local/bin && sudo port -v selfupdate;
+ sudo port -N install openssl redis;
+ fi;
+
+addons:
+ apt:
+ packages:
+ - libc6-dbg
+ - libc6-dev
+ - libc6:i386
+ - libc6-dev-i386
+ - libc6-dbg:i386
+ - gcc-multilib
+ - g++-multilib
+ - libssl-dev
+ - libssl-dev:i386
+ - valgrind
+
+env:
+ - BITS="32"
+ - BITS="64"
+
+script:
+ - EXTRA_CMAKE_OPTS="-DENABLE_EXAMPLES:BOOL=ON -DENABLE_SSL:BOOL=ON -DENABLE_SSL_TESTS:BOOL=ON";
+ if [ "$TRAVIS_OS_NAME" == "osx" ]; then
+ if [ "$BITS" == "32" ]; then
+ CFLAGS="-m32 -Werror";
+ CXXFLAGS="-m32 -Werror";
+ LDFLAGS="-m32";
+ EXTRA_CMAKE_OPTS=;
+ else
+ CFLAGS="-Werror";
+ CXXFLAGS="-Werror";
+ fi;
+ else
+ TEST_PREFIX="valgrind --track-origins=yes --leak-check=full";
+ if [ "$BITS" == "32" ]; then
+ CFLAGS="-m32 -Werror";
+ CXXFLAGS="-m32 -Werror";
+ LDFLAGS="-m32";
+ EXTRA_CMAKE_OPTS=;
+ else
+ CFLAGS="-Werror";
+ CXXFLAGS="-Werror";
+ fi;
+ fi;
+ export CFLAGS CXXFLAGS LDFLAGS TEST_PREFIX EXTRA_CMAKE_OPTS
+ - make && make clean;
+ if [ "$TRAVIS_OS_NAME" == "osx" ]; then
+ if [ "$BITS" == "64" ]; then
+ OPENSSL_PREFIX="$(ls -d /usr/local/Cellar/openssl@1.1/*)" USE_SSL=1 make;
+ fi;
+ else
+ USE_SSL=1 make;
+ fi;
+ - mkdir build/ && cd build/
+ - cmake .. ${EXTRA_CMAKE_OPTS}
+ - make VERBOSE=1
+ - if [ "$BITS" == "64" ]; then
+ TEST_SSL=1 SKIPS_AS_FAILS=1 ctest -V;
+ else
+ SKIPS_AS_FAILS=1 ctest -V;
+ fi;
+
+jobs:
+ include:
+ # Windows MinGW cross compile on Linux
+ - os: linux
+ dist: xenial
+ compiler: mingw
+ addons:
+ apt:
+ packages:
+ - ninja-build
+ - gcc-mingw-w64-x86-64
+ - g++-mingw-w64-x86-64
+ script:
+ - mkdir build && cd build
+ - CC=x86_64-w64-mingw32-gcc CXX=x86_64-w64-mingw32-g++ cmake .. -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_BUILD_WITH_INSTALL_RPATH=on
+ - ninja -v
+
+ # Windows MSVC 2017
+ - os: windows
+ compiler: msvc
+ env:
+ - MATRIX_EVAL="CC=cl.exe && CXX=cl.exe"
+ before_install:
+ - eval "${MATRIX_EVAL}"
+ install:
+ - choco install ninja
+ - choco install -y memurai-developer
+ script:
+ - mkdir build && cd build
+ - cmd.exe //C 'C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools\VC\Auxiliary\Build\vcvarsall.bat' amd64 '&&'
+ cmake .. -G Ninja -DCMAKE_BUILD_TYPE=Release -DENABLE_EXAMPLES=ON '&&' ninja -v
+ - ./hiredis-test.exe
diff --git a/deps/hiredis/CHANGELOG.md b/deps/hiredis/CHANGELOG.md
new file mode 100644
index 0000000..2a2bc31
--- /dev/null
+++ b/deps/hiredis/CHANGELOG.md
@@ -0,0 +1,364 @@
+## [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)
+
+<span style="color:red">This release erroneously bumped the SONAME, please use [1.0.2](https://github.com/redis/hiredis/tree/v1.0.2)</span>
+
+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:
+
+_A big thanks to everyone who helped with this release. The following list includes everyone who contributed at least five lines, sorted by lines contributed._ :sparkling_heart:
+
+[Michael Grunder](https://github.com/michael-grunder), [Yossi Gottlieb](https://github.com/yossigo),
+[Mark Nunberg](https://github.com/mnunberg), [Marcus Geelnard](https://github.com/mbitsnbites),
+[Justin Brewer](https://github.com/justinbrewer), [Valentino Geron](https://github.com/valentinogeron),
+[Minun Dragonation](https://github.com/dragonation), [Omri Steiner](https://github.com/OmriSteiner),
+[Sangmoon Yi](https://github.com/jman-krafton), [Jinjiazh](https://github.com/jinjiazhang),
+[Odin Hultgren Van Der Horst](https://github.com/Miniwoffer), [Muhammad Zahalqa](https://github.com/tryfinally),
+[Nick Rivera](https://github.com/heronr), [Qi Yang](https://github.com/movebean),
+[kevin1018](https://github.com/kevin1018)
+
+[Full Changelog](https://github.com/redis/hiredis/compare/v0.14.1...v1.0.0)
+
+**BREAKING CHANGES**:
+
+* `redisOptions` now has two timeout fields. One for connecting, and one for commands. If you're presently using `options->timeout` you will need to change it to use `options->connect_timeout`. (See [example](https://github.com/redis/hiredis/commit/38b5ae543f5c99eb4ccabbe277770fc6bc81226f#diff-86ba39d37aa829c8c82624cce4f049fbL36))
+
+* Bulk and multi-bulk lengths less than -1 or greater than `LLONG_MAX` are now protocol errors. This is consistent
+ with the RESP specification. On 32-bit platforms, the upper bound is lowered to `SIZE_MAX`.
+
+* `redisReplyObjectFunctions.createArray` now takes `size_t` for its length parameter.
+
+**New features:**
+- Support for RESP3
+ [\#697](https://github.com/redis/hiredis/pull/697),
+ [\#805](https://github.com/redis/hiredis/pull/805),
+ [\#819](https://github.com/redis/hiredis/pull/819),
+ [\#841](https://github.com/redis/hiredis/pull/841)
+ ([Yossi Gottlieb](https://github.com/yossigo), [Michael Grunder](https://github.com/michael-grunder))
+- Support for SSL connections
+ [\#645](https://github.com/redis/hiredis/pull/645),
+ [\#699](https://github.com/redis/hiredis/pull/699),
+ [\#702](https://github.com/redis/hiredis/pull/702),
+ [\#708](https://github.com/redis/hiredis/pull/708),
+ [\#711](https://github.com/redis/hiredis/pull/711),
+ [\#821](https://github.com/redis/hiredis/pull/821),
+ [more](https://github.com/redis/hiredis/pulls?q=is%3Apr+is%3Amerged+SSL)
+ ([Mark Nunberg](https://github.com/mnunberg), [Yossi Gottlieb](https://github.com/yossigo))
+- Run-time allocator injection
+ [\#800](https://github.com/redis/hiredis/pull/800)
+ ([Michael Grunder](https://github.com/michael-grunder))
+- Improved Windows support (including MinGW and Windows CI)
+ [\#652](https://github.com/redis/hiredis/pull/652),
+ [\#663](https://github.com/redis/hiredis/pull/663)
+ ([Marcus Geelnard](https://www.bitsnbites.eu/author/m/))
+- Adds support for distinct connect and command timeouts
+ [\#839](https://github.com/redis/hiredis/pull/839),
+ [\#829](https://github.com/redis/hiredis/pull/829)
+ ([Valentino Geron](https://github.com/valentinogeron))
+- Add generic pointer and destructor to `redisContext` that users can use for context.
+ [\#855](https://github.com/redis/hiredis/pull/855)
+ ([Michael Grunder](https://github.com/michael-grunder))
+
+**Closed issues (that involved code changes):**
+
+- Makefile does not install TLS libraries [\#809](https://github.com/redis/hiredis/issues/809)
+- redisConnectWithOptions should not set command timeout [\#722](https://github.com/redis/hiredis/issues/722), [\#829](https://github.com/redis/hiredis/pull/829) ([valentinogeron](https://github.com/valentinogeron))
+- Fix integer overflow in `sdsrange` [\#827](https://github.com/redis/hiredis/issues/827)
+- INFO & CLUSTER commands failed when using RESP3 [\#802](https://github.com/redis/hiredis/issues/802)
+- Windows compatibility patches [\#687](https://github.com/redis/hiredis/issues/687), [\#838](https://github.com/redis/hiredis/issues/838), [\#842](https://github.com/redis/hiredis/issues/842)
+- RESP3 PUSH messages incorrectly use pending callback [\#825](https://github.com/redis/hiredis/issues/825)
+- Asynchronous PSUBSCRIBE command fails when using RESP3 [\#815](https://github.com/redis/hiredis/issues/815)
+- New SSL API [\#804](https://github.com/redis/hiredis/issues/804), [\#813](https://github.com/redis/hiredis/issues/813)
+- Hard-coded limit of nested reply depth [\#794](https://github.com/redis/hiredis/issues/794)
+- Fix TCP_NODELAY in Windows/OSX [\#679](https://github.com/redis/hiredis/issues/679), [\#690](https://github.com/redis/hiredis/issues/690), [\#779](https://github.com/redis/hiredis/issues/779), [\#785](https://github.com/redis/hiredis/issues/785),
+- Added timers to libev adapter. [\#778](https://github.com/redis/hiredis/issues/778), [\#795](https://github.com/redis/hiredis/pull/795)
+- Initialization discards const qualifier [\#777](https://github.com/redis/hiredis/issues/777)
+- \[BUG\]\[MinGW64\] Error setting socket timeout [\#775](https://github.com/redis/hiredis/issues/775)
+- undefined reference to hi_malloc [\#769](https://github.com/redis/hiredis/issues/769)
+- hiredis pkg-config file incorrectly ignores multiarch libdir spec'n [\#767](https://github.com/redis/hiredis/issues/767)
+- Don't use -G to build shared object on Solaris [\#757](https://github.com/redis/hiredis/issues/757)
+- error when make USE\_SSL=1 [\#748](https://github.com/redis/hiredis/issues/748)
+- Allow to change SSL Mode [\#646](https://github.com/redis/hiredis/issues/646)
+- hiredis/adapters/libevent.h memleak [\#618](https://github.com/redis/hiredis/issues/618)
+- redisLibuvPoll crash when server closes the connetion [\#545](https://github.com/redis/hiredis/issues/545)
+- about redisAsyncDisconnect question [\#518](https://github.com/redis/hiredis/issues/518)
+- hiredis adapters libuv error for help [\#508](https://github.com/redis/hiredis/issues/508)
+- API/ABI changes analysis [\#506](https://github.com/redis/hiredis/issues/506)
+- Memory leak patch in Redis [\#502](https://github.com/redis/hiredis/issues/502)
+- Remove the depth limitation [\#421](https://github.com/redis/hiredis/issues/421)
+
+**Merged pull requests:**
+
+- Move SSL management to a distinct private pointer [\#855](https://github.com/redis/hiredis/pull/855) ([michael-grunder](https://github.com/michael-grunder))
+- Move include to sockcompat.h to maintain style [\#850](https://github.com/redis/hiredis/pull/850) ([michael-grunder](https://github.com/michael-grunder))
+- Remove erroneous tag and add license to push example [\#849](https://github.com/redis/hiredis/pull/849) ([michael-grunder](https://github.com/michael-grunder))
+- fix windows compiling with mingw [\#848](https://github.com/redis/hiredis/pull/848) ([rmalizia44](https://github.com/rmalizia44))
+- Some Windows quality of life improvements. [\#846](https://github.com/redis/hiredis/pull/846) ([michael-grunder](https://github.com/michael-grunder))
+- Use \_WIN32 define instead of WIN32 [\#845](https://github.com/redis/hiredis/pull/845) ([michael-grunder](https://github.com/michael-grunder))
+- Non Linux CI fixes [\#844](https://github.com/redis/hiredis/pull/844) ([michael-grunder](https://github.com/michael-grunder))
+- Resp3 oob push support [\#841](https://github.com/redis/hiredis/pull/841) ([michael-grunder](https://github.com/michael-grunder))
+- fix \#785: defer TCP\_NODELAY in async tcp connections [\#836](https://github.com/redis/hiredis/pull/836) ([OmriSteiner](https://github.com/OmriSteiner))
+- sdsrange overflow fix [\#830](https://github.com/redis/hiredis/pull/830) ([michael-grunder](https://github.com/michael-grunder))
+- Use explicit pointer casting for c++ compatibility [\#826](https://github.com/redis/hiredis/pull/826) ([aureus1](https://github.com/aureus1))
+- Document allocator injection and completeness fix in test.c [\#824](https://github.com/redis/hiredis/pull/824) ([michael-grunder](https://github.com/michael-grunder))
+- Use unique names for allocator struct members [\#823](https://github.com/redis/hiredis/pull/823) ([michael-grunder](https://github.com/michael-grunder))
+- New SSL API to replace redisSecureConnection\(\). [\#821](https://github.com/redis/hiredis/pull/821) ([yossigo](https://github.com/yossigo))
+- Add logic to handle RESP3 push messages [\#819](https://github.com/redis/hiredis/pull/819) ([michael-grunder](https://github.com/michael-grunder))
+- Use standrad isxdigit instead of custom helper function. [\#814](https://github.com/redis/hiredis/pull/814) ([tryfinally](https://github.com/tryfinally))
+- Fix missing SSL build/install options. [\#812](https://github.com/redis/hiredis/pull/812) ([yossigo](https://github.com/yossigo))
+- Add link to ABI tracker [\#808](https://github.com/redis/hiredis/pull/808) ([michael-grunder](https://github.com/michael-grunder))
+- Resp3 verbatim string support [\#805](https://github.com/redis/hiredis/pull/805) ([michael-grunder](https://github.com/michael-grunder))
+- Allow users to replace allocator and handle OOM everywhere. [\#800](https://github.com/redis/hiredis/pull/800) ([michael-grunder](https://github.com/michael-grunder))
+- Remove nested depth limitation. [\#797](https://github.com/redis/hiredis/pull/797) ([michael-grunder](https://github.com/michael-grunder))
+- Attempt to fix compilation on Solaris [\#796](https://github.com/redis/hiredis/pull/796) ([michael-grunder](https://github.com/michael-grunder))
+- Support timeouts in libev adapater [\#795](https://github.com/redis/hiredis/pull/795) ([michael-grunder](https://github.com/michael-grunder))
+- Fix pkgconfig when installing to a custom lib dir [\#793](https://github.com/redis/hiredis/pull/793) ([michael-grunder](https://github.com/michael-grunder))
+- Fix USE\_SSL=1 make/cmake on OSX and CMake tests [\#789](https://github.com/redis/hiredis/pull/789) ([michael-grunder](https://github.com/michael-grunder))
+- Use correct libuv call on Windows [\#784](https://github.com/redis/hiredis/pull/784) ([michael-grunder](https://github.com/michael-grunder))
+- Added CMake package config and fixed hiredis\_ssl on Windows [\#783](https://github.com/redis/hiredis/pull/783) ([michael-grunder](https://github.com/michael-grunder))
+- CMake: Set hiredis\_ssl shared object version. [\#780](https://github.com/redis/hiredis/pull/780) ([yossigo](https://github.com/yossigo))
+- Win32 tests and timeout fix [\#776](https://github.com/redis/hiredis/pull/776) ([michael-grunder](https://github.com/michael-grunder))
+- Provides an optional cleanup callback for async data. [\#768](https://github.com/redis/hiredis/pull/768) ([heronr](https://github.com/heronr))
+- Housekeeping fixes [\#764](https://github.com/redis/hiredis/pull/764) ([michael-grunder](https://github.com/michael-grunder))
+- install alloc.h [\#756](https://github.com/redis/hiredis/pull/756) ([ch1aki](https://github.com/ch1aki))
+- fix spelling mistakes [\#746](https://github.com/redis/hiredis/pull/746) ([ShooterIT](https://github.com/ShooterIT))
+- Free the reply in redisGetReply when passed NULL [\#741](https://github.com/redis/hiredis/pull/741) ([michael-grunder](https://github.com/michael-grunder))
+- Fix dead code in sslLogCallback relating to should\_log variable. [\#737](https://github.com/redis/hiredis/pull/737) ([natoscott](https://github.com/natoscott))
+- Fix typo in dict.c. [\#731](https://github.com/redis/hiredis/pull/731) ([Kevin-Xi](https://github.com/Kevin-Xi))
+- Adding an option to DISABLE\_TESTS [\#727](https://github.com/redis/hiredis/pull/727) ([pbotros](https://github.com/pbotros))
+- Update README with SSL support. [\#720](https://github.com/redis/hiredis/pull/720) ([yossigo](https://github.com/yossigo))
+- Fixes leaks in unit tests [\#715](https://github.com/redis/hiredis/pull/715) ([michael-grunder](https://github.com/michael-grunder))
+- SSL Tests [\#711](https://github.com/redis/hiredis/pull/711) ([yossigo](https://github.com/yossigo))
+- SSL Reorganization [\#708](https://github.com/redis/hiredis/pull/708) ([yossigo](https://github.com/yossigo))
+- Fix MSVC build. [\#706](https://github.com/redis/hiredis/pull/706) ([yossigo](https://github.com/yossigo))
+- SSL: Properly report SSL\_connect\(\) errors. [\#702](https://github.com/redis/hiredis/pull/702) ([yossigo](https://github.com/yossigo))
+- Silent SSL trace to stdout by default. [\#699](https://github.com/redis/hiredis/pull/699) ([yossigo](https://github.com/yossigo))
+- Port RESP3 support from Redis. [\#697](https://github.com/redis/hiredis/pull/697) ([yossigo](https://github.com/yossigo))
+- Removed whitespace before newline [\#691](https://github.com/redis/hiredis/pull/691) ([Miniwoffer](https://github.com/Miniwoffer))
+- Add install adapters header files [\#688](https://github.com/redis/hiredis/pull/688) ([kevin1018](https://github.com/kevin1018))
+- Remove unnecessary null check before free [\#684](https://github.com/redis/hiredis/pull/684) ([qlyoung](https://github.com/qlyoung))
+- redisReaderGetReply leak memory [\#671](https://github.com/redis/hiredis/pull/671) ([movebean](https://github.com/movebean))
+- fix timeout code in windows [\#670](https://github.com/redis/hiredis/pull/670) ([jman-krafton](https://github.com/jman-krafton))
+- test: fix errstr matching for musl libc [\#665](https://github.com/redis/hiredis/pull/665) ([ghost](https://github.com/ghost))
+- Windows: MinGW fixes and Windows Travis builders [\#663](https://github.com/redis/hiredis/pull/663) ([mbitsnbites](https://github.com/mbitsnbites))
+- The setsockopt and getsockopt API diffs from BSD socket and WSA one [\#662](https://github.com/redis/hiredis/pull/662) ([dragonation](https://github.com/dragonation))
+- Fix Compile Error On Windows \(Visual Studio\) [\#658](https://github.com/redis/hiredis/pull/658) ([jinjiazhang](https://github.com/jinjiazhang))
+- Fix NXDOMAIN test case [\#653](https://github.com/redis/hiredis/pull/653) ([michael-grunder](https://github.com/michael-grunder))
+- Add MinGW support [\#652](https://github.com/redis/hiredis/pull/652) ([mbitsnbites](https://github.com/mbitsnbites))
+- SSL Support [\#645](https://github.com/redis/hiredis/pull/645) ([mnunberg](https://github.com/mnunberg))
+- Fix Invalid argument after redisAsyncConnectUnix [\#644](https://github.com/redis/hiredis/pull/644) ([codehz](https://github.com/codehz))
+- Makefile: use predefined AR [\#632](https://github.com/redis/hiredis/pull/632) ([Mic92](https://github.com/Mic92))
+- FreeBSD build fix [\#628](https://github.com/redis/hiredis/pull/628) ([devnexen](https://github.com/devnexen))
+- Fix errors not propagating properly with libuv.h. [\#624](https://github.com/redis/hiredis/pull/624) ([yossigo](https://github.com/yossigo))
+- Update README.md [\#621](https://github.com/redis/hiredis/pull/621) ([Crunsher](https://github.com/Crunsher))
+- Fix redisBufferRead documentation [\#620](https://github.com/redis/hiredis/pull/620) ([hacst](https://github.com/hacst))
+- Add CPPFLAGS to REAL\_CFLAGS [\#614](https://github.com/redis/hiredis/pull/614) ([thomaslee](https://github.com/thomaslee))
+- Update createArray to take size\_t [\#597](https://github.com/redis/hiredis/pull/597) ([justinbrewer](https://github.com/justinbrewer))
+- fix common realloc mistake and add null check more [\#580](https://github.com/redis/hiredis/pull/580) ([charsyam](https://github.com/charsyam))
+- Proper error reporting for connect failures [\#578](https://github.com/redis/hiredis/pull/578) ([mnunberg](https://github.com/mnunberg))
+
+\* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)*
+
+## [1.0.0-rc1](https://github.com/redis/hiredis/tree/v1.0.0-rc1) - (2020-07-29)
+
+_Note: There were no changes to code between v1.0.0-rc1 and v1.0.0 so see v1.0.0 for changelog_
+
+### 0.14.1 (2020-03-13)
+
+* Adds safe allocation wrappers (CVE-2020-7105, #747, #752) (Michael Grunder)
+
+### 0.14.0 (2018-09-25)
+**BREAKING CHANGES**:
+
+* Change `redisReply.len` to `size_t`, as it denotes the the size of a string
+
+ User code should compare this to `size_t` values as well.
+ If it was used to compare to other values, casting might be necessary or can be removed, if casting was applied before.
+
+* Make string2ll static to fix conflict with Redis (Tom Lee [c3188b])
+* Use -dynamiclib instead of -shared for OSX (Ryan Schmidt [a65537])
+* Use string2ll from Redis w/added tests (Michael Grunder [7bef04, 60f622])
+* Makefile - OSX compilation fixes (Ryan Schmidt [881fcb, 0e9af8])
+* Remove redundant NULL checks (Justin Brewer [54acc8, 58e6b8])
+* Fix bulk and multi-bulk length truncation (Justin Brewer [109197])
+* Fix SIGSEGV in OpenBSD by checking for NULL before calling freeaddrinfo (Justin Brewer [546d94])
+* Several POSIX compatibility fixes (Justin Brewer [bbeab8, 49bbaa, d1c1b6])
+* Makefile - Compatibility fixes (Dimitri Vorobiev [3238cf, 12a9d1])
+* Makefile - Fix make install on FreeBSD (Zach Shipko [a2ef2b])
+* Makefile - don't assume $(INSTALL) is cp (Igor Gnatenko [725a96])
+* Separate side-effect causing function from assert and small cleanup (amallia [b46413, 3c3234])
+* Don't send negative values to `__redisAsyncCommand` (Frederik Deweerdt [706129])
+* Fix leak if setsockopt fails (Frederik Deweerdt [e21c9c])
+* Fix libevent leak (zfz [515228])
+* Clean up GCC warning (Ichito Nagata [2ec774])
+* Keep track of errno in `__redisSetErrorFromErrno()` as snprintf may use it (Jin Qing [25cd88])
+* Solaris compilation fix (Donald Whyte [41b07d])
+* Reorder linker arguments when building examples (Tustfarm-heart [06eedd])
+* Keep track of subscriptions in case of rapid subscribe/unsubscribe (Hyungjin Kim [073dc8, be76c5, d46999])
+* libuv use after free fix (Paul Scott [cbb956])
+* Properly close socket fd on reconnect attempt (WSL [64d1ec])
+* Skip valgrind in OSX tests (Jan-Erik Rediger [9deb78])
+* Various updates for Travis testing OSX (Ted Nyman [fa3774, 16a459, bc0ea5])
+* Update libevent (Chris Xin [386802])
+* Change sds.h for building in C++ projects (Ali Volkan ATLI [f5b32e])
+* Use proper format specifier in redisFormatSdsCommandArgv (Paulino Huerta, Jan-Erik Rediger [360a06, 8655a6])
+* Better handling of NULL reply in example code (Jan-Erik Rediger [1b8ed3])
+* Prevent overflow when formatting an error (Jan-Erik Rediger [0335cb])
+* Compatibility fix for strerror_r (Tom Lee [bb1747])
+* Properly detect integer parse/overflow errors (Justin Brewer [93421f])
+* Adds CI for Windows and cygwin fixes (owent, [6c53d6, 6c3e40])
+* Catch a buffer overflow when formatting the error message
+* Import latest upstream sds. This breaks applications that are linked against the old hiredis v0.13
+* Fix warnings, when compiled with -Wshadow
+* Make hiredis compile in Cygwin on Windows, now CI-tested
+* Bulk and multi-bulk lengths less than -1 or greater than `LLONG_MAX` are now
+ protocol errors. This is consistent with the RESP specification. On 32-bit
+ platforms, the upper bound is lowered to `SIZE_MAX`.
+
+* Remove backwards compatibility macro's
+
+This removes the following old function aliases, use the new name now:
+
+| Old | New |
+| --------------------------- | ---------------------- |
+| redisReplyReaderCreate | redisReaderCreate |
+| redisReplyReaderCreate | redisReaderCreate |
+| redisReplyReaderFree | redisReaderFree |
+| redisReplyReaderFeed | redisReaderFeed |
+| redisReplyReaderGetReply | redisReaderGetReply |
+| redisReplyReaderSetPrivdata | redisReaderSetPrivdata |
+| redisReplyReaderGetObject | redisReaderGetObject |
+| redisReplyReaderGetError | redisReaderGetError |
+
+* The `DEBUG` variable in the Makefile was renamed to `DEBUG_FLAGS`
+
+Previously it broke some builds for people that had `DEBUG` set to some arbitrary value,
+due to debugging other software.
+By renaming we avoid unintentional name clashes.
+
+Simply rename `DEBUG` to `DEBUG_FLAGS` in your environment to make it working again.
+
+### 0.13.3 (2015-09-16)
+
+* Revert "Clear `REDIS_CONNECTED` flag when connection is closed".
+* Make tests pass on FreeBSD (Thanks, Giacomo Olgeni)
+
+
+If the `REDIS_CONNECTED` flag is cleared,
+the async onDisconnect callback function will never be called.
+This causes problems as the disconnect is never reported back to the user.
+
+### 0.13.2 (2015-08-25)
+
+* Prevent crash on pending replies in async code (Thanks, @switch-st)
+* Clear `REDIS_CONNECTED` flag when connection is closed (Thanks, Jerry Jacobs)
+* Add MacOS X addapter (Thanks, @dizzus)
+* Add Qt adapter (Thanks, Pietro Cerutti)
+* Add Ivykis adapter (Thanks, Gergely Nagy)
+
+All adapters are provided as is and are only tested where possible.
+
+### 0.13.1 (2015-05-03)
+
+This is a bug fix release.
+The new `reconnect` method introduced new struct members, which clashed with pre-defined names in pre-C99 code.
+Another commit forced C99 compilation just to make it work, but of course this is not desirable for outside projects.
+Other non-C99 code can now use hiredis as usual again.
+Sorry for the inconvenience.
+
+* Fix memory leak in async reply handling (Salvatore Sanfilippo)
+* Rename struct member to avoid name clash with pre-c99 code (Alex Balashov, ncopa)
+
+### 0.13.0 (2015-04-16)
+
+This release adds a minimal Windows compatibility layer.
+The parser, standalone since v0.12.0, can now be compiled on Windows
+(and thus used in other client libraries as well)
+
+* Windows compatibility layer for parser code (tzickel)
+* Properly escape data printed to PKGCONF file (Dan Skorupski)
+* Fix tests when assert() undefined (Keith Bennett, Matt Stancliff)
+* Implement a reconnect method for the client context, this changes the structure of `redisContext` (Aaron Bedra)
+
+### 0.12.1 (2015-01-26)
+
+* Fix `make install`: DESTDIR support, install all required files, install PKGCONF in proper location
+* Fix `make test` as 32 bit build on 64 bit platform
+
+### 0.12.0 (2015-01-22)
+
+* Add optional KeepAlive support
+
+* Try again on EINTR errors
+
+* Add libuv adapter
+
+* Add IPv6 support
+
+* Remove possibility of multiple close on same fd
+
+* Add ability to bind source address on connect
+
+* Add redisConnectFd() and redisFreeKeepFd()
+
+* Fix getaddrinfo() memory leak
+
+* Free string if it is unused (fixes memory leak)
+
+* Improve redisAppendCommandArgv performance 2.5x
+
+* Add support for SO_REUSEADDR
+
+* Fix redisvFormatCommand format parsing
+
+* Add GLib 2.0 adapter
+
+* Refactor reading code into read.c
+
+* Fix errno error buffers to not clobber errors
+
+* Generate pkgconf during build
+
+* Silence _BSD_SOURCE warnings
+
+* Improve digit counting for multibulk creation
+
+
+### 0.11.0
+
+* Increase the maximum multi-bulk reply depth to 7.
+
+* Increase the read buffer size from 2k to 16k.
+
+* Use poll(2) instead of select(2) to support large fds (>= 1024).
+
+### 0.10.1
+
+* Makefile overhaul. Important to check out if you override one or more
+ variables using environment variables or via arguments to the "make" tool.
+
+* Issue #45: Fix potential memory leak for a multi bulk reply with 0 elements
+ being created by the default reply object functions.
+
+* Issue #43: Don't crash in an asynchronous context when Redis returns an error
+ reply after the connection has been made (this happens when the maximum
+ number of connections is reached).
+
+### 0.10.0
+
+* See commit log.
diff --git a/deps/hiredis/CMakeLists.txt b/deps/hiredis/CMakeLists.txt
new file mode 100644
index 0000000..fe6720b
--- /dev/null
+++ b/deps/hiredis/CMakeLists.txt
@@ -0,0 +1,245 @@
+CMAKE_MINIMUM_REQUIRED(VERSION 3.4.0)
+
+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_ASYNC_TESTS "Should we run all asynchronous API tests" OFF)
+
+MACRO(getVersionBit name)
+ SET(VERSION_REGEX "^#define ${name} (.+)$")
+ FILE(STRINGS "${CMAKE_CURRENT_SOURCE_DIR}/hiredis.h"
+ VERSION_BIT REGEX ${VERSION_REGEX})
+ STRING(REGEX REPLACE ${VERSION_REGEX} "\\1" ${name} "${VERSION_BIT}")
+ENDMACRO(getVersionBit)
+
+getVersionBit(HIREDIS_MAJOR)
+getVersionBit(HIREDIS_MINOR)
+getVersionBit(HIREDIS_PATCH)
+getVersionBit(HIREDIS_SONAME)
+SET(VERSION "${HIREDIS_MAJOR}.${HIREDIS_MINOR}.${HIREDIS_PATCH}")
+MESSAGE("Detected 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")
+
+SET(hiredis_sources
+ alloc.c
+ async.c
+ dict.c
+ hiredis.c
+ net.c
+ read.c
+ sds.c
+ sockcompat.c)
+
+SET(hiredis_sources ${hiredis_sources})
+
+IF(WIN32)
+ ADD_COMPILE_DEFINITIONS(_CRT_SECURE_NO_WARNINGS WIN32_LEAN_AND_MEAN)
+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 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 $<INSTALL_INTERFACE:include> $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>)
+TARGET_INCLUDE_DIRECTORIES(hiredis_static PUBLIC $<INSTALL_INTERFACE:include> $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>)
+
+CONFIGURE_FILE(hiredis.pc.in hiredis.pc @ONLY)
+
+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 $<TARGET_PDB_FILE:hiredis>
+ DESTINATION ${CMAKE_INSTALL_BINDIR}
+ CONFIGURATIONS Debug RelWithDebInfo)
+ INSTALL(FILES $<TARGET_FILE_DIR:hiredis_static>/$<TARGET_FILE_BASE_NAME:hiredis_static>.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)
+
+export(EXPORT hiredis-targets
+ FILE "${CMAKE_CURRENT_BINARY_DIR}/hiredis-targets.cmake"
+ NAMESPACE hiredis::)
+
+SET(CMAKE_CONF_INSTALL_DIR share/hiredis)
+SET(INCLUDE_INSTALL_DIR include)
+include(CMakePackageConfigHelpers)
+configure_package_config_file(hiredis-config.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/hiredis-config.cmake
+ INSTALL_DESTINATION ${CMAKE_CONF_INSTALL_DIR}
+ PATH_VARS INCLUDE_INSTALL_DIR)
+
+INSTALL(EXPORT hiredis-targets
+ FILE hiredis-targets.cmake
+ NAMESPACE hiredis::
+ DESTINATION ${CMAKE_CONF_INSTALL_DIR})
+
+INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/hiredis-config.cmake
+ DESTINATION ${CMAKE_CONF_INSTALL_DIR})
+
+
+IF(ENABLE_SSL)
+ IF (NOT OPENSSL_ROOT_DIR)
+ IF (APPLE)
+ SET(OPENSSL_ROOT_DIR "/usr/local/opt/openssl")
+ ENDIF()
+ ENDIF()
+ FIND_PACKAGE(OpenSSL REQUIRED)
+ SET(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")
+ ENDIF()
+
+ SET_TARGET_PROPERTIES(hiredis_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 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 $<TARGET_PDB_FILE:hiredis_ssl>
+ DESTINATION ${CMAKE_INSTALL_BINDIR}
+ CONFIGURATIONS Debug RelWithDebInfo)
+ INSTALL(FILES $<TARGET_FILE_DIR:hiredis_ssl_static>/$<TARGET_FILE_BASE_NAME:hiredis_ssl_static>.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)
+
+ export(EXPORT hiredis_ssl-targets
+ FILE "${CMAKE_CURRENT_BINARY_DIR}/hiredis_ssl-targets.cmake"
+ NAMESPACE hiredis::)
+
+ SET(CMAKE_CONF_INSTALL_DIR share/hiredis_ssl)
+ configure_package_config_file(hiredis_ssl-config.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/hiredis_ssl-config.cmake
+ INSTALL_DESTINATION ${CMAKE_CONF_INSTALL_DIR}
+ PATH_VARS INCLUDE_INSTALL_DIR)
+
+ INSTALL(EXPORT hiredis_ssl-targets
+ FILE hiredis_ssl-targets.cmake
+ NAMESPACE hiredis::
+ DESTINATION ${CMAKE_CONF_INSTALL_DIR})
+
+ INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/hiredis_ssl-config.cmake
+ DESTINATION ${CMAKE_CONF_INSTALL_DIR})
+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_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)
+ENDIF()
+
+# Add examples
+IF(ENABLE_EXAMPLES)
+ ADD_SUBDIRECTORY(examples)
+ENDIF(ENABLE_EXAMPLES)
diff --git a/deps/hiredis/COPYING b/deps/hiredis/COPYING
new file mode 100644
index 0000000..a5fc973
--- /dev/null
+++ b/deps/hiredis/COPYING
@@ -0,0 +1,29 @@
+Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com>
+Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com>
+
+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.
diff --git a/deps/hiredis/Makefile b/deps/hiredis/Makefile
new file mode 100644
index 0000000..a2ad84c
--- /dev/null
+++ b/deps/hiredis/Makefile
@@ -0,0 +1,338 @@
+# Hiredis Makefile
+# Copyright (C) 2010-2011 Salvatore Sanfilippo <antirez at gmail dot com>
+# Copyright (C) 2010-2011 Pieter Noordhuis <pcnoordhuis at gmail dot com>
+# 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
+EXAMPLES=hiredis-example hiredis-example-libevent hiredis-example-libev hiredis-example-glib hiredis-example-push
+TESTS=hiredis-test
+LIBNAME=libhiredis
+PKGCONFNAME=hiredis.pc
+
+HIREDIS_MAJOR=$(shell grep HIREDIS_MAJOR hiredis.h | awk '{print $$3}')
+HIREDIS_MINOR=$(shell grep HIREDIS_MINOR hiredis.h | awk '{print $$3}')
+HIREDIS_PATCH=$(shell grep HIREDIS_PATCH hiredis.h | awk '{print $$3}')
+HIREDIS_SONAME=$(shell grep HIREDIS_SONAME hiredis.h | awk '{print $$3}')
+
+# Installation related variables and target
+PREFIX?=/usr/local
+INCLUDE_PATH?=include/hiredis
+LIBRARY_PATH?=lib
+PKGCONF_PATH?=pkgconfig
+INSTALL_INCLUDE_PATH= $(DESTDIR)$(PREFIX)/$(INCLUDE_PATH)
+INSTALL_LIBRARY_PATH= $(DESTDIR)$(PREFIX)/$(LIBRARY_PATH)
+INSTALL_PKGCONF_PATH= $(INSTALL_LIBRARY_PATH)/$(PKGCONF_PATH)
+
+# redis-server configuration used for testing
+REDIS_PORT=56379
+REDIS_SERVER=redis-server
+define REDIS_TEST_CONFIG
+ daemonize yes
+ pidfile /tmp/hiredis-test-redis.pid
+ port $(REDIS_PORT)
+ bind 127.0.0.1
+ unixsocket /tmp/hiredis-test-redis.sock
+endef
+export REDIS_TEST_CONFIG
+
+# Fallback to gcc when $CC is not in $PATH.
+CC:=$(shell sh -c 'type $${CC%% *} >/dev/null 2>/dev/null && echo $(CC) || echo gcc')
+CXX:=$(shell sh -c 'type $${CXX%% *} >/dev/null 2>/dev/null && echo $(CXX) || echo g++')
+OPTIMIZATION?=-O3
+WARNINGS=-Wall -W -Wstrict-prototypes -Wwrite-strings -Wno-missing-field-initializers
+DEBUG_FLAGS?= -g -ggdb
+REAL_CFLAGS=$(OPTIMIZATION) -fPIC $(CPPFLAGS) $(CFLAGS) $(WARNINGS) $(DEBUG_FLAGS)
+REAL_LDFLAGS=$(LDFLAGS)
+
+DYLIBSUFFIX=so
+STLIBSUFFIX=a
+DYLIB_MINOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_SONAME)
+DYLIB_MAJOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_MAJOR)
+DYLIBNAME=$(LIBNAME).$(DYLIBSUFFIX)
+
+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')
+
+# This is required for test.c only
+ifeq ($(TEST_ASYNC),1)
+ export CFLAGS+=-DHIREDIS_TEST_ASYNC
+endif
+
+ifeq ($(USE_SSL),1)
+ 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),FreeBSD)
+ LDFLAGS+=-lm
+ IS_GCC=$(shell sh -c '$(CC) --version 2>/dev/null |egrep -i -c "gcc"')
+ ifeq ($(IS_GCC),1)
+ REAL_CFLAGS+=-pedantic
+ endif
+else
+ REAL_CFLAGS+=-pedantic
+endif
+
+ifeq ($(uname_S),SunOS)
+ IS_SUN_CC=$(shell sh -c '$(CC) -V 2>&1 |egrep -i -c "sun|studio"')
+ ifeq ($(IS_SUN_CC),1)
+ SUN_SHARED_FLAG=-G
+ else
+ SUN_SHARED_FLAG=-shared
+ endif
+ REAL_LDFLAGS+= -ldl -lnsl -lsocket
+ DYLIB_MAKE_CMD=$(CC) $(SUN_SHARED_FLAG) -o $(DYLIBNAME) -h $(DYLIB_MINOR_NAME) $(LDFLAGS)
+ SSL_DYLIB_MAKE_CMD=$(CC) $(SUN_SHARED_FLAG) -o $(SSL_DYLIBNAME) -h $(SSL_DYLIB_MINOR_NAME) $(LDFLAGS) $(SSL_LDFLAGS)
+endif
+ifeq ($(uname_S),Darwin)
+ DYLIBSUFFIX=dylib
+ DYLIB_MINOR_NAME=$(LIBNAME).$(HIREDIS_SONAME).$(DYLIBSUFFIX)
+ DYLIB_MAKE_CMD=$(CC) -dynamiclib -Wl,-install_name,$(PREFIX)/$(LIBRARY_PATH)/$(DYLIB_MINOR_NAME) -o $(DYLIBNAME) $(LDFLAGS)
+ SSL_DYLIB_MAKE_CMD=$(CC) -dynamiclib -Wl,-install_name,$(PREFIX)/$(LIBRARY_PATH)/$(SSL_DYLIB_MINOR_NAME) -o $(SSL_DYLIBNAME) $(LDFLAGS) $(SSL_LDFLAGS)
+ DYLIB_PLUGIN=-Wl,-undefined -Wl,dynamic_lookup
+endif
+
+all: dynamic static 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
+async.o: async.c fmacros.h alloc.h async.h hiredis.h read.h sds.h net.h dict.c dict.h win32.h async_private.h
+dict.o: dict.c fmacros.h alloc.h dict.h
+hiredis.o: hiredis.c fmacros.h hiredis.h read.h sds.h alloc.h net.h async.h win32.h
+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
+test.o: test.c fmacros.h hiredis.h read.h sds.h alloc.h net.h sockcompat.h win32.h
+
+$(DYLIBNAME): $(OBJ)
+ $(DYLIB_MAKE_CMD) -o $(DYLIBNAME) $(OBJ) $(REAL_LDFLAGS)
+
+$(STLIBNAME): $(OBJ)
+ $(STLIB_MAKE_CMD) $(STLIBNAME) $(OBJ)
+
+#################### SSL building rules start ####################
+$(SSL_DYLIBNAME): $(SSL_OBJ)
+ $(SSL_DYLIB_MAKE_CMD) $(DYLIB_PLUGIN) -o $(SSL_DYLIBNAME) $(SSL_OBJ) $(REAL_LDFLAGS) $(LDFLAGS) $(SSL_LDFLAGS)
+
+$(SSL_STLIBNAME): $(SSL_OBJ)
+ $(STLIB_MAKE_CMD) $(SSL_STLIBNAME) $(SSL_OBJ)
+
+$(SSL_OBJ): ssl.c 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)
+ $(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< -levent $(STLIBNAME) $(REAL_LDFLAGS)
+
+hiredis-example-libevent-ssl: examples/example-libevent-ssl.c adapters/libevent.h $(STLIBNAME) $(SSL_STLIBNAME)
+ $(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< -levent $(STLIBNAME) $(SSL_STLIBNAME) $(REAL_LDFLAGS) $(SSL_LDFLAGS)
+
+hiredis-example-libev: examples/example-libev.c adapters/libev.h $(STLIBNAME)
+ $(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< -lev $(STLIBNAME) $(REAL_LDFLAGS)
+
+hiredis-example-glib: examples/example-glib.c adapters/glib.h $(STLIBNAME)
+ $(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< $(shell pkg-config --cflags --libs glib-2.0) $(STLIBNAME) $(REAL_LDFLAGS)
+
+hiredis-example-ivykis: examples/example-ivykis.c adapters/ivykis.h $(STLIBNAME)
+ $(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< -livykis $(STLIBNAME) $(REAL_LDFLAGS)
+
+hiredis-example-macosx: examples/example-macosx.c adapters/macosx.h $(STLIBNAME)
+ $(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< -framework CoreFoundation $(STLIBNAME) $(REAL_LDFLAGS)
+
+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. <redis repository>/src)"
+ @false
+else
+hiredis-example-ae: examples/example-ae.c adapters/ae.h $(STLIBNAME)
+ $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. -I$(AE_DIR) $< $(AE_DIR)/ae.o $(AE_DIR)/zmalloc.o $(AE_DIR)/../deps/jemalloc/lib/libjemalloc.a -pthread $(STLIBNAME)
+endif
+
+ifndef LIBUV_DIR
+# dynamic link libuv.so
+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
+
+ifeq ($(and $(QT_MOC),$(QT_INCLUDE_DIR),$(QT_LIBRARY_DIR)),)
+hiredis-example-qt:
+ @echo "Please specify QT_MOC, QT_INCLUDE_DIR AND QT_LIBRARY_DIR"
+ @false
+else
+hiredis-example-qt: examples/example-qt.cpp adapters/qt.h $(STLIBNAME)
+ $(QT_MOC) adapters/qt.h -I. -I$(QT_INCLUDE_DIR) -I$(QT_INCLUDE_DIR)/QtCore | \
+ $(CXX) -x c++ -o qt-adapter-moc.o -c - $(REAL_CFLAGS) -I. -I$(QT_INCLUDE_DIR) -I$(QT_INCLUDE_DIR)/QtCore
+ $(QT_MOC) examples/example-qt.h -I. -I$(QT_INCLUDE_DIR) -I$(QT_INCLUDE_DIR)/QtCore | \
+ $(CXX) -x c++ -o qt-example-moc.o -c - $(REAL_CFLAGS) -I. -I$(QT_INCLUDE_DIR) -I$(QT_INCLUDE_DIR)/QtCore
+ $(CXX) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. -I$(QT_INCLUDE_DIR) -I$(QT_INCLUDE_DIR)/QtCore -L$(QT_LIBRARY_DIR) qt-adapter-moc.o qt-example-moc.o $< -pthread $(STLIBNAME) -lQtCore
+endif
+
+hiredis-example: examples/example.c $(STLIBNAME)
+ $(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< $(STLIBNAME) $(REAL_LDFLAGS)
+
+hiredis-example-push: examples/example-push.c $(STLIBNAME)
+ $(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< $(STLIBNAME) $(REAL_LDFLAGS)
+
+examples: $(EXAMPLES)
+
+TEST_LIBS = $(STLIBNAME) $(SSL_STLIB)
+TEST_LDFLAGS = $(SSL_LDFLAGS)
+ifeq ($(USE_SSL),1)
+ TEST_LDFLAGS += -pthread
+endif
+ifeq ($(TEST_ASYNC),1)
+ TEST_LDFLAGS += -levent
+endif
+
+hiredis-test: test.o $(TEST_LIBS)
+ $(CC) -o $@ $(REAL_CFLAGS) -I. $^ $(REAL_LDFLAGS) $(TEST_LDFLAGS)
+
+hiredis-%: %.o $(STLIBNAME)
+ $(CC) $(REAL_CFLAGS) -o $@ $< $(TEST_LIBS) $(REAL_LDFLAGS)
+
+test: hiredis-test
+ ./hiredis-test
+
+check: hiredis-test
+ TEST_SSL=$(USE_SSL) ./test.sh
+
+.c.o:
+ $(CC) -std=c99 -c $(REAL_CFLAGS) $<
+
+clean:
+ rm -rf $(DYLIBNAME) $(STLIBNAME) $(SSL_DYLIBNAME) $(SSL_STLIBNAME) $(TESTS) $(PKGCONFNAME) examples/hiredis-example* *.o *.gcda *.gcno *.gcov
+
+dep:
+ $(CC) $(CPPFLAGS) $(CFLAGS) -MM *.c
+
+INSTALL?= cp -pPR
+
+$(PKGCONFNAME): hiredis.h
+ @echo "Generating $@ for pkgconfig..."
+ @echo prefix=$(PREFIX) > $@
+ @echo exec_prefix=\$${prefix} >> $@
+ @echo libdir=$(PREFIX)/$(LIBRARY_PATH) >> $@
+ @echo includedir=$(PREFIX)/$(INCLUDE_PATH) >> $@
+ @echo >> $@
+ @echo Name: hiredis >> $@
+ @echo Description: Minimalistic C client library for Redis. >> $@
+ @echo Version: $(HIREDIS_MAJOR).$(HIREDIS_MINOR).$(HIREDIS_PATCH) >> $@
+ @echo Libs: -L\$${libdir} -lhiredis >> $@
+ @echo Cflags: -I\$${includedir} -D_FILE_OFFSET_BITS=64 >> $@
+
+$(SSL_PKGCONFNAME): hiredis_ssl.h
+ @echo "Generating $@ for pkgconfig..."
+ @echo prefix=$(PREFIX) > $@
+ @echo exec_prefix=\$${prefix} >> $@
+ @echo libdir=$(PREFIX)/$(LIBRARY_PATH) >> $@
+ @echo includedir=$(PREFIX)/$(INCLUDE_PATH) >> $@
+ @echo >> $@
+ @echo Name: hiredis_ssl >> $@
+ @echo Description: SSL Support for hiredis. >> $@
+ @echo Version: $(HIREDIS_MAJOR).$(HIREDIS_MINOR).$(HIREDIS_PATCH) >> $@
+ @echo Requires: hiredis >> $@
+ @echo Libs: -L\$${libdir} -lhiredis_ssl >> $@
+ @echo Libs.private: -lssl -lcrypto >> $@
+
+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
+ $(INSTALL) $(DYLIBNAME) $(INSTALL_LIBRARY_PATH)/$(DYLIB_MINOR_NAME)
+ cd $(INSTALL_LIBRARY_PATH) && ln -sf $(DYLIB_MINOR_NAME) $(DYLIBNAME)
+ $(INSTALL) $(STLIBNAME) $(INSTALL_LIBRARY_PATH)
+ mkdir -p $(INSTALL_PKGCONF_PATH)
+ $(INSTALL) $(PKGCONFNAME) $(INSTALL_PKGCONF_PATH)
+
+install-ssl: $(SSL_DYLIBNAME) $(SSL_STLIBNAME) $(SSL_PKGCONFNAME)
+ mkdir -p $(INSTALL_INCLUDE_PATH) $(INSTALL_LIBRARY_PATH)
+ $(INSTALL) hiredis_ssl.h $(INSTALL_INCLUDE_PATH)
+ $(INSTALL) $(SSL_DYLIBNAME) $(INSTALL_LIBRARY_PATH)/$(SSL_DYLIB_MINOR_NAME)
+ cd $(INSTALL_LIBRARY_PATH) && ln -sf $(SSL_DYLIB_MINOR_NAME) $(SSL_DYLIBNAME)
+ $(INSTALL) $(SSL_STLIBNAME) $(INSTALL_LIBRARY_PATH)
+ mkdir -p $(INSTALL_PKGCONF_PATH)
+ $(INSTALL) $(SSL_PKGCONFNAME) $(INSTALL_PKGCONF_PATH)
+
+32bit:
+ @echo ""
+ @echo "WARNING: if this fails under Linux you probably need to install libc6-dev-i386"
+ @echo ""
+ $(MAKE) CFLAGS="-m32" LDFLAGS="-m32"
+
+32bit-vars:
+ $(eval CFLAGS=-m32)
+ $(eval LDFLAGS=-m32)
+
+gprof:
+ $(MAKE) CFLAGS="-pg" LDFLAGS="-pg"
+
+gcov:
+ $(MAKE) CFLAGS+="-fprofile-arcs -ftest-coverage" LDFLAGS="-fprofile-arcs"
+
+coverage: gcov
+ make check
+ mkdir -p tmp/lcov
+ lcov -d . -c --exclude '/usr*' -o tmp/lcov/hiredis.info
+ genhtml --legend -o tmp/lcov/report tmp/lcov/hiredis.info
+
+noopt:
+ $(MAKE) OPTIMIZATION=""
+
+.PHONY: all test check clean dep install 32bit 32bit-vars gprof gcov noopt
diff --git a/deps/hiredis/README.md b/deps/hiredis/README.md
new file mode 100644
index 0000000..ed66220
--- /dev/null
+++ b/deps/hiredis/README.md
@@ -0,0 +1,671 @@
+
+[![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](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
+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*.
+
+## Upgrading to `1.0.2`
+
+<span style="color:red">NOTE: v1.0.1 erroneously bumped SONAME, which is why it is skipped here.</span>
+
+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.
+It includes some minor breaking changes, mostly to make the exposed API more uniform and self-explanatory.
+It also bundles the updated `sds` library, to sync up with upstream and Redis.
+For code changes see the [Changelog](CHANGELOG.md).
+
+_Note: As described below, a few member names have been changed but most applications should be able to upgrade with minor code changes and recompiling._
+
+## IMPORTANT: Breaking changes from `0.14.1` -> `1.0.0`
+
+* `redisContext` has two additional members (`free_privdata`, and `privctx`).
+* `redisOptions.timeout` has been renamed to `redisOptions.connect_timeout`, and we've added `redisOptions.command_timeout`.
+* `redisReplyObjectFunctions.createArray` now takes `size_t` instead of `int` for its length parameter.
+
+## IMPORTANT: Breaking changes when upgrading from 0.13.x -> 0.14.x
+
+Bulk and multi-bulk lengths less than -1 or greater than `LLONG_MAX` are now
+protocol errors. This is consistent with the RESP specification. On 32-bit
+platforms, the upper bound is lowered to `SIZE_MAX`.
+
+Change `redisReply.len` to `size_t`, as it denotes the the size of a string
+
+User code should compare this to `size_t` values as well. If it was used to
+compare to other values, casting might be necessary or can be removed, if
+casting was applied before.
+
+## Upgrading from `<0.9.0`
+
+Version 0.9.0 is a major overhaul of hiredis in every aspect. However, upgrading existing
+code using hiredis should not be a big pain. The key thing to keep in mind when
+upgrading is that hiredis >= 0.9.0 uses a `redisContext*` to keep state, in contrast to
+the stateless 0.0.1 that only has a file descriptor to work with.
+
+## Synchronous API
+
+To consume the synchronous API, there are only a few function calls that need to be introduced:
+
+```c
+redisContext *redisConnect(const char *ip, int port);
+void *redisCommand(redisContext *c, const char *format, ...);
+void freeReplyObject(void *reply);
+```
+
+### Connecting
+
+The function `redisConnect` is used to create a so-called `redisContext`. The
+context is where Hiredis holds state for a connection. The `redisContext`
+struct has an integer `err` field that is non-zero when the connection is in
+an error state. The field `errstr` will contain a string with a description of
+the error. More information on errors can be found in the **Errors** section.
+After trying to connect to Redis using `redisConnect` you should
+check the `err` field to see if establishing the connection was successful:
+```c
+redisContext *c = redisConnect("127.0.0.1", 6379);
+if (c == NULL || c->err) {
+ if (c) {
+ printf("Error: %s\n", c->errstr);
+ // handle error
+ } else {
+ printf("Can't allocate redis context\n");
+ }
+}
+```
+
+*Note: A `redisContext` is not thread-safe.*
+
+### Sending commands
+
+There are several ways to issue commands to Redis. The first that will be introduced is
+`redisCommand`. This function takes a format similar to printf. In the simplest form,
+it is used like this:
+```c
+reply = redisCommand(context, "SET foo bar");
+```
+
+The specifier `%s` interpolates a string in the command, and uses `strlen` to
+determine the length of the string:
+```c
+reply = redisCommand(context, "SET foo %s", value);
+```
+When you need to pass binary safe strings in a command, the `%b` specifier can be
+used. Together with a pointer to the string, it requires a `size_t` length argument
+of the string:
+```c
+reply = redisCommand(context, "SET foo %b", value, (size_t) valuelen);
+```
+Internally, Hiredis splits the command in different arguments and will
+convert it to the protocol used to communicate with Redis.
+One or more spaces separates arguments, so you can use the specifiers
+anywhere in an argument:
+```c
+reply = redisCommand(context, "SET key:%s %s", myid, value);
+```
+
+### Using replies
+
+The return value of `redisCommand` holds a reply when the command was
+successfully executed. When an error occurs, the return value is `NULL` and
+the `err` field in the context will be set (see section on **Errors**).
+Once an error is returned the context cannot be reused and you should set up
+a new connection.
+
+The standard replies that `redisCommand` are of the type `redisReply`. The
+`type` field in the `redisReply` should be used to test what kind of reply
+was received:
+
+### RESP2
+
+* **`REDIS_REPLY_STATUS`**:
+ * The command replied with a status reply. The status string can be accessed using `reply->str`.
+ The length of this string can be accessed using `reply->len`.
+
+* **`REDIS_REPLY_ERROR`**:
+ * The command replied with an error. The error string can be accessed identical to `REDIS_REPLY_STATUS`.
+
+* **`REDIS_REPLY_INTEGER`**:
+ * The command replied with an integer. The integer value can be accessed using the
+ `reply->integer` field of type `long long`.
+
+* **`REDIS_REPLY_NIL`**:
+ * The command replied with a **nil** object. There is no data to access.
+
+* **`REDIS_REPLY_STRING`**:
+ * A bulk (string) reply. The value of the reply can be accessed using `reply->str`.
+ The length of this string can be accessed using `reply->len`.
+
+* **`REDIS_REPLY_ARRAY`**:
+ * A multi bulk reply. The number of elements in the multi bulk reply is stored in
+ `reply->elements`. Every element in the multi bulk reply is a `redisReply` object as well
+ and can be accessed via `reply->element[..index..]`.
+ Redis may reply with nested arrays but this is fully supported.
+
+### RESP3
+
+Hiredis also supports every new `RESP3` data type which are as follows. For more information about the protocol see the `RESP3` [specification.](https://github.com/antirez/RESP3/blob/master/spec.md)
+
+* **`REDIS_REPLY_DOUBLE`**:
+ * The command replied with a double-precision floating point number.
+ The value is stored as a string in the `str` member, and can be converted with `strtod` or similar.
+
+* **`REDIS_REPLY_BOOL`**:
+ * A boolean true/false reply.
+ The value is stored in the `integer` member and will be either `0` or `1`.
+
+* **`REDIS_REPLY_MAP`**:
+ * An array with the added invariant that there will always be an even number of elements.
+ 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.
+ Like the MAP type, the data is identical to an array response except there are no duplicate values.
+
+* **`REDIS_REPLY_PUSH`**:
+ * An array that can be generated spontaneously by Redis.
+ This array response will always contain at least two subelements. The first contains the type of `PUSH` message (e.g. `message`, or `invalidate`), and the second being a sub-array with the `PUSH` payload itself.
+
+* **`REDIS_REPLY_ATTR`**:
+ * An array structurally identical to a `MAP` but intended as meta-data about a reply.
+ _As of Redis 6.0.6 this reply type is not used in Redis_
+
+* **`REDIS_REPLY_BIGNUM`**:
+ * A string representing an arbitrarily large signed or unsigned integer value.
+ The number will be encoded as a string in the `str` member of `redisReply`.
+
+* **`REDIS_REPLY_VERB`**:
+ * A verbatim string, intended to be presented to the user without modification.
+ 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
+contained in arrays and nested arrays, so there is no need for the user to
+free the sub replies (it is actually harmful and will corrupt the memory).
+
+**Important:** the current version of hiredis (1.0.0) frees replies when the
+asynchronous API is used. This means you should not call `freeReplyObject` when
+you use this API. The reply is cleaned up by hiredis _after_ the callback
+returns. We may introduce a flag to make this configurable in future versions of the library.
+
+### Cleaning up
+
+To disconnect and free the context the following function can be used:
+```c
+void redisFree(redisContext *c);
+```
+This function immediately closes the socket and then frees the allocations done in
+creating the context.
+
+### Sending commands (cont'd)
+
+Together with `redisCommand`, the function `redisCommandArgv` can be used to issue commands.
+It has the following prototype:
+```c
+void *redisCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen);
+```
+It takes the number of arguments `argc`, an array of strings `argv` and the lengths of the
+arguments `argvlen`. For convenience, `argvlen` may be set to `NULL` and the function will
+use `strlen(3)` on every argument to determine its length. Obviously, when any of the arguments
+need to be binary safe, the entire array of lengths `argvlen` should be provided.
+
+The return value has the same semantic as `redisCommand`.
+
+### Pipelining
+
+To explain how Hiredis supports pipelining in a blocking connection, there needs to be
+understanding of the internal execution flow.
+
+When any of the functions in the `redisCommand` family is called, Hiredis first formats the
+command according to the Redis protocol. The formatted command is then put in the output buffer
+of the context. This output buffer is dynamic, so it can hold any number of commands.
+After the command is put in the output buffer, `redisGetReply` is called. This function has the
+following two execution paths:
+
+1. The input buffer is non-empty:
+ * Try to parse a single reply from the input buffer and return it
+ * If no reply could be parsed, continue at *2*
+2. The input buffer is empty:
+ * Write the **entire** output buffer to the socket
+ * Read from the socket until a single reply could be parsed
+
+The function `redisGetReply` is exported as part of the Hiredis API and can be used when a reply
+is expected on the socket. To pipeline commands, the only things that needs to be done is
+filling up the output buffer. For this cause, two commands can be used that are identical
+to the `redisCommand` family, apart from not returning a reply:
+```c
+void redisAppendCommand(redisContext *c, const char *format, ...);
+void redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen);
+```
+After calling either function one or more times, `redisGetReply` can be used to receive the
+subsequent replies. The return value for this function is either `REDIS_OK` or `REDIS_ERR`, where
+the latter means an error occurred while reading a reply. Just as with the other commands,
+the `err` field in the context can be used to find out what the cause of this error is.
+
+The following examples shows a simple pipeline (resulting in only a single call to `write(2)` and
+a single call to `read(2)`):
+```c
+redisReply *reply;
+redisAppendCommand(context,"SET foo bar");
+redisAppendCommand(context,"GET foo");
+redisGetReply(context,(void**)&reply); // reply for SET
+freeReplyObject(reply);
+redisGetReply(context,(void**)&reply); // reply for GET
+freeReplyObject(reply);
+```
+This API can also be used to implement a blocking subscriber:
+```c
+reply = redisCommand(context,"SUBSCRIBE foo");
+freeReplyObject(reply);
+while(redisGetReply(context,(void *)&reply) == REDIS_OK) {
+ // consume message
+ freeReplyObject(reply);
+}
+```
+### Errors
+
+When a function call is not successful, depending on the function either `NULL` or `REDIS_ERR` is
+returned. The `err` field inside the context will be non-zero and set to one of the
+following constants:
+
+* **`REDIS_ERR_IO`**:
+ There was an I/O error while creating the connection, trying to write
+ to the socket or read from the socket. If you included `errno.h` in your
+ application, you can use the global `errno` variable to find out what is
+ wrong.
+
+* **`REDIS_ERR_EOF`**:
+ The server closed the connection which resulted in an empty read.
+
+* **`REDIS_ERR_PROTOCOL`**:
+ There was an error while parsing the protocol.
+
+* **`REDIS_ERR_OTHER`**:
+ Any other error. Currently, it is only used when a specified hostname to connect
+ to cannot be resolved.
+
+In every case, the `errstr` field in the context will be set to hold a string representation
+of the error.
+
+## Asynchronous API
+
+Hiredis comes with an asynchronous API that works easily with any event library.
+Examples are bundled that show using Hiredis with [libev](http://software.schmorp.de/pkg/libev.html)
+and [libevent](http://monkey.org/~provos/libevent/).
+
+### Connecting
+
+The function `redisAsyncConnect` can be used to establish a non-blocking connection to
+Redis. It returns a pointer to the newly created `redisAsyncContext` struct. The `err` field
+should be checked after creation to see if there were errors creating the connection.
+Because the connection that will be created is non-blocking, the kernel is not able to
+instantly return if the specified host and port is able to accept a connection.
+
+*Note: A `redisAsyncContext` is not thread-safe.*
+
+```c
+redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379);
+if (c->err) {
+ printf("Error: %s\n", c->errstr);
+ // handle error
+}
+```
+
+The asynchronous context can hold a disconnect callback function that is called when the
+connection is disconnected (either because of an error or per user request). This function should
+have the following prototype:
+```c
+void(const redisAsyncContext *c, int status);
+```
+On a disconnect, the `status` argument is set to `REDIS_OK` when disconnection was initiated by the
+user, or `REDIS_ERR` when the disconnection was caused by an error. When it is `REDIS_ERR`, the `err`
+field in the context can be accessed to find out the cause of the error.
+
+The context object is always freed after the disconnect callback fired. When a reconnect is needed,
+the disconnect callback is a good point to do so.
+
+Setting the disconnect callback can only be done once per context. For subsequent calls it will
+return `REDIS_ERR`. The function to set the disconnect callback has the following prototype:
+```c
+int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn);
+```
+`ac->data` may be used to pass user data to this callback, the same can be done for redisConnectCallback.
+### Sending commands and their callbacks
+
+In an asynchronous context, commands are automatically pipelined due to the nature of an event loop.
+Therefore, unlike the synchronous API, there is only a single way to send commands.
+Because commands are sent to Redis asynchronously, issuing a command requires a callback function
+that is called when the reply is received. Reply callbacks should have the following prototype:
+```c
+void(redisAsyncContext *c, void *reply, void *privdata);
+```
+The `privdata` argument can be used to curry arbitrary data to the callback from the point where
+the command is initially queued for execution.
+
+The functions that can be used to issue commands in an asynchronous context are:
+```c
+int redisAsyncCommand(
+ redisAsyncContext *ac, redisCallbackFn *fn, void *privdata,
+ const char *format, ...);
+int redisAsyncCommandArgv(
+ redisAsyncContext *ac, redisCallbackFn *fn, void *privdata,
+ int argc, const char **argv, const size_t *argvlen);
+```
+Both functions work like their blocking counterparts. The return value is `REDIS_OK` when the command
+was successfully added to the output buffer and `REDIS_ERR` otherwise. Example: when the connection
+is being disconnected per user-request, no new commands may be added to the output buffer and `REDIS_ERR` is
+returned on calls to the `redisAsyncCommand` family.
+
+If the reply for a command with a `NULL` callback is read, it is immediately freed. When the callback
+for a command is non-`NULL`, the memory is freed immediately following the callback: the reply is only
+valid for the duration of the callback.
+
+All pending callbacks are called with a `NULL` reply when the context encountered an error.
+
+### Disconnecting
+
+An asynchronous connection can be terminated using:
+```c
+void redisAsyncDisconnect(redisAsyncContext *ac);
+```
+When this function is called, the connection is **not** immediately terminated. Instead, new
+commands are no longer accepted and the connection is only terminated when all pending commands
+have been written to the socket, their respective replies have been read and their respective
+callbacks have been executed. After this, the disconnection callback is executed with the
+`REDIS_OK` status and the context object is freed.
+
+### Hooking it up to event library *X*
+
+There are a few hooks that need to be set on the context object after it is created.
+See the `adapters/` directory for bindings to *libev* and *libevent*.
+
+## Reply parsing API
+
+Hiredis comes with a reply parsing API that makes it easy for writing higher
+level language bindings.
+
+The reply parsing API consists of the following functions:
+```c
+redisReader *redisReaderCreate(void);
+void redisReaderFree(redisReader *reader);
+int redisReaderFeed(redisReader *reader, const char *buf, size_t len);
+int redisReaderGetReply(redisReader *reader, void **reply);
+```
+The same set of functions are used internally by hiredis when creating a
+normal Redis context, the above API just exposes it to the user for a direct
+usage.
+
+### Usage
+
+The function `redisReaderCreate` creates a `redisReader` structure that holds a
+buffer with unparsed data and state for the protocol parser.
+
+Incoming data -- most likely from a socket -- can be placed in the internal
+buffer of the `redisReader` using `redisReaderFeed`. This function will make a
+copy of the buffer pointed to by `buf` for `len` bytes. This data is parsed
+when `redisReaderGetReply` is called. This function returns an integer status
+and a reply object (as described above) via `void **reply`. The returned status
+can be either `REDIS_OK` or `REDIS_ERR`, where the latter means something went
+wrong (either a protocol error, or an out of memory error).
+
+The parser limits the level of nesting for multi bulk payloads to 7. If the
+multi bulk nesting level is higher than this, the parser returns an error.
+
+### Customizing replies
+
+The function `redisReaderGetReply` creates `redisReply` and makes the function
+argument `reply` point to the created `redisReply` variable. For instance, if
+the response of type `REDIS_REPLY_STATUS` then the `str` field of `redisReply`
+will hold the status as a vanilla C string. However, the functions that are
+responsible for creating instances of the `redisReply` can be customized by
+setting the `fn` field on the `redisReader` struct. This should be done
+immediately after creating the `redisReader`.
+
+For example, [hiredis-rb](https://github.com/pietern/hiredis-rb/blob/master/ext/hiredis_ext/reader.c)
+uses customized reply object functions to create Ruby objects.
+
+### Reader max buffer
+
+Both when using the Reader API directly or when using it indirectly via a
+normal Redis context, the redisReader structure uses a buffer in order to
+accumulate data from the server.
+Usually this buffer is destroyed when it is empty and is larger than 16
+KiB in order to avoid wasting memory in unused buffers
+
+However when working with very big payloads destroying the buffer may slow
+down performances considerably, so it is possible to modify the max size of
+an idle buffer changing the value of the `maxbuf` field of the reader structure
+to the desired value. The special value of 0 means that there is no maximum
+value for an idle buffer, so the buffer will never get freed.
+
+For instance if you have a normal Redis context you can set the maximum idle
+buffer to zero (unlimited) just with:
+```c
+context->reader->maxbuf = 0;
+```
+This should be done only in order to maximize performances when working with
+large payloads. The context should be set back to `REDIS_READER_MAX_BUF` again
+as soon as possible in order to prevent allocation of useless memory.
+
+### Reader max array elements
+
+By default the hiredis reply parser sets the maximum number of multi-bulk elements
+to 2^32 - 1 or 4,294,967,295 entries. If you need to process multi-bulk replies
+with more than this many elements you can set the value higher or to zero, meaning
+unlimited with:
+```c
+context->reader->maxelements = 0;
+```
+
+## SSL/TLS Support
+
+### Building
+
+SSL/TLS support is not built by default and requires an explicit flag:
+
+ make USE_SSL=1
+
+This requires OpenSSL development package (e.g. including header files to be
+available.
+
+When enabled, SSL/TLS support is built into extra `libhiredis_ssl.a` and
+`libhiredis_ssl.so` static/dynamic libraries. This leaves the original libraries
+unaffected so no additional dependencies are introduced.
+
+### Using it
+
+First, you'll need to make sure you include the SSL header file:
+
+```c
+#include "hiredis.h"
+#include "hiredis_ssl.h"
+```
+
+You will also need to link against `libhiredis_ssl`, **in addition** to
+`libhiredis` and add `-lssl -lcrypto` to satisfy its dependencies.
+
+Hiredis implements SSL/TLS on top of its normal `redisContext` or
+`redisAsyncContext`, so you will need to establish a connection first and then
+initiate an SSL/TLS handshake.
+
+#### Hiredis OpenSSL Wrappers
+
+Before Hiredis can negotiate an SSL/TLS connection, it is necessary to
+initialize OpenSSL and create a context. You can do that in two ways:
+
+1. Work directly with the OpenSSL API to initialize the library's global context
+ and create `SSL_CTX *` and `SSL *` contexts. With an `SSL *` object you can
+ call `redisInitiateSSL()`.
+2. Work with a set of Hiredis-provided wrappers around OpenSSL, create a
+ `redisSSLContext` object to hold configuration and use
+ `redisInitiateSSLWithContext()` to initiate the SSL/TLS handshake.
+
+```c
+/* An Hiredis SSL context. It holds SSL configuration and can be reused across
+ * many contexts.
+ */
+redisSSLContext *ssl_context;
+
+/* An error variable to indicate what went wrong, if the context fails to
+ * initialize.
+ */
+redisSSLContextError ssl_error;
+
+/* Initialize global OpenSSL state.
+ *
+ * You should call this only once when your app initializes, and only if
+ * you don't explicitly or implicitly initialize OpenSSL it elsewhere.
+ */
+redisInitOpenSSL();
+
+/* Create SSL context */
+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);
+
+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);
+if (c == NULL || c->err) {
+ /* Handle error and abort... */
+}
+
+/* Negotiate SSL/TLS */
+if (redisInitiateSSLWithContext(c, ssl_context) != REDIS_OK) {
+ /* Handle error, in c->err / c->errstr */
+}
+```
+
+## RESP3 PUSH replies
+Redis 6.0 introduced PUSH replies with the reply-type `>`. These messages are generated spontaneously and can arrive at any time, so must be handled using callbacks.
+
+### Default behavior
+Hiredis installs handlers on `redisContext` and `redisAsyncContext` by default, which will intercept and free any PUSH replies detected. This means existing code will work as-is after upgrading to Redis 6 and switching to `RESP3`.
+
+### Custom PUSH handler prototypes
+The callback prototypes differ between `redisContext` and `redisAsyncContext`.
+
+#### redisContext
+```c
+void my_push_handler(void *privdata, void *reply) {
+ /* Handle the reply */
+
+ /* Note: We need to free the reply in our custom handler for
+ blocking contexts. This lets us keep the reply if
+ we want. */
+ freeReplyObject(reply);
+}
+```
+
+#### redisAsyncContext
+```c
+void my_async_push_handler(redisAsyncContext *ac, void *reply) {
+ /* Handle the reply */
+
+ /* Note: Because async hiredis always frees replies, you should
+ not call freeReplyObject in an async push callback. */
+}
+```
+
+### Installing a custom handler
+There are two ways to set your own PUSH handlers.
+
+1. Set `push_cb` or `async_push_cb` in the `redisOptions` struct and connect with `redisConnectWithOptions` or `redisAsyncConnectWithOptions`.
+ ```c
+ redisOptions = {0};
+ REDIS_OPTIONS_SET_TCP(&options, "127.0.0.1", 6379);
+ options->push_cb = my_push_handler;
+ redisContext *context = redisConnectWithOptions(&options);
+ ```
+2. Call `redisSetPushCallback` or `redisAsyncSetPushCallback` on a connected context.
+ ```c
+ redisContext *context = redisConnect("127.0.0.1", 6379);
+ redisSetPushCallback(context, my_push_handler);
+ ```
+
+ _Note `redisSetPushCallback` and `redisAsyncSetPushCallback` both return any currently configured handler, making it easy to override and then return to the old value._
+
+### Specifying no handler
+If you have a unique use-case where you don't want hiredis to automatically intercept and free PUSH replies, you will want to configure no handler at all. This can be done in two ways.
+1. Set the `REDIS_OPT_NO_PUSH_AUTOFREE` flag in `redisOptions` and leave the callback function pointer `NULL`.
+ ```c
+ redisOptions = {0};
+ REDIS_OPTIONS_SET_TCP(&options, "127.0.0.1", 6379);
+ options->options |= REDIS_OPT_NO_PUSH_AUTOFREE;
+ redisContext *context = redisConnectWithOptions(&options);
+ ```
+3. Call `redisSetPushCallback` with `NULL` once connected.
+ ```c
+ redisContext *context = redisConnect("127.0.0.1", 6379);
+ redisSetPushCallback(context, NULL);
+ ```
+
+ _Note: With no handler configured, calls to `redisCommand` may generate more than one reply, so this strategy is only applicable when there's some kind of blocking`redisGetReply()` loop (e.g. `MONITOR` or `SUBSCRIBE` workloads)._
+
+## Allocator injection
+
+Hiredis uses a pass-thru structure of function pointers defined in [alloc.h](https://github.com/redis/hiredis/blob/f5d25850/alloc.h#L41) that contain the currently configured allocation and deallocation functions. By default they just point to libc (`malloc`, `calloc`, `realloc`, etc).
+
+### Overriding
+
+One can override the allocators like so:
+
+```c
+hiredisAllocFuncs myfuncs = {
+ .mallocFn = my_malloc,
+ .callocFn = my_calloc,
+ .reallocFn = my_realloc,
+ .strdupFn = my_strdup,
+ .freeFn = my_free,
+};
+
+// Override allocators (function returns current allocators if needed)
+hiredisAllocFuncs orig = hiredisSetAllocators(&myfuncs);
+```
+
+To reset the allocators to their default libc function simply call:
+
+```c
+hiredisResetAllocators();
+```
+
+## AUTHORS
+
+Salvatore Sanfilippo (antirez at gmail),\
+Pieter Noordhuis (pcnoordhuis at gmail)\
+Michael Grunder (michael dot grunder at gmail)
+
+_Hiredis is released under the BSD license._
diff --git a/deps/hiredis/adapters/ae.h b/deps/hiredis/adapters/ae.h
new file mode 100644
index 0000000..660d82e
--- /dev/null
+++ b/deps/hiredis/adapters/ae.h
@@ -0,0 +1,130 @@
+/*
+ * Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com>
+ *
+ * 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.
+ */
+
+#ifndef __HIREDIS_AE_H__
+#define __HIREDIS_AE_H__
+#include <sys/types.h>
+#include <ae.h>
+#include "../hiredis.h"
+#include "../async.h"
+
+typedef struct redisAeEvents {
+ redisAsyncContext *context;
+ aeEventLoop *loop;
+ int fd;
+ int reading, writing;
+} redisAeEvents;
+
+static void redisAeReadEvent(aeEventLoop *el, int fd, void *privdata, int mask) {
+ ((void)el); ((void)fd); ((void)mask);
+
+ redisAeEvents *e = (redisAeEvents*)privdata;
+ redisAsyncHandleRead(e->context);
+}
+
+static void redisAeWriteEvent(aeEventLoop *el, int fd, void *privdata, int mask) {
+ ((void)el); ((void)fd); ((void)mask);
+
+ redisAeEvents *e = (redisAeEvents*)privdata;
+ redisAsyncHandleWrite(e->context);
+}
+
+static void redisAeAddRead(void *privdata) {
+ redisAeEvents *e = (redisAeEvents*)privdata;
+ aeEventLoop *loop = e->loop;
+ if (!e->reading) {
+ e->reading = 1;
+ aeCreateFileEvent(loop,e->fd,AE_READABLE,redisAeReadEvent,e);
+ }
+}
+
+static void redisAeDelRead(void *privdata) {
+ redisAeEvents *e = (redisAeEvents*)privdata;
+ aeEventLoop *loop = e->loop;
+ if (e->reading) {
+ e->reading = 0;
+ aeDeleteFileEvent(loop,e->fd,AE_READABLE);
+ }
+}
+
+static void redisAeAddWrite(void *privdata) {
+ redisAeEvents *e = (redisAeEvents*)privdata;
+ aeEventLoop *loop = e->loop;
+ if (!e->writing) {
+ e->writing = 1;
+ aeCreateFileEvent(loop,e->fd,AE_WRITABLE,redisAeWriteEvent,e);
+ }
+}
+
+static void redisAeDelWrite(void *privdata) {
+ redisAeEvents *e = (redisAeEvents*)privdata;
+ aeEventLoop *loop = e->loop;
+ if (e->writing) {
+ e->writing = 0;
+ aeDeleteFileEvent(loop,e->fd,AE_WRITABLE);
+ }
+}
+
+static void redisAeCleanup(void *privdata) {
+ redisAeEvents *e = (redisAeEvents*)privdata;
+ redisAeDelRead(privdata);
+ redisAeDelWrite(privdata);
+ hi_free(e);
+}
+
+static int redisAeAttach(aeEventLoop *loop, redisAsyncContext *ac) {
+ redisContext *c = &(ac->c);
+ redisAeEvents *e;
+
+ /* Nothing should be attached when something is already attached */
+ if (ac->ev.data != NULL)
+ return REDIS_ERR;
+
+ /* Create container for context and r/w events */
+ e = (redisAeEvents*)hi_malloc(sizeof(*e));
+ if (e == NULL)
+ return REDIS_ERR;
+
+ e->context = ac;
+ e->loop = loop;
+ e->fd = c->fd;
+ e->reading = e->writing = 0;
+
+ /* Register functions to start/stop listening for events */
+ ac->ev.addRead = redisAeAddRead;
+ ac->ev.delRead = redisAeDelRead;
+ ac->ev.addWrite = redisAeAddWrite;
+ ac->ev.delWrite = redisAeDelWrite;
+ ac->ev.cleanup = redisAeCleanup;
+ ac->ev.data = e;
+
+ return REDIS_OK;
+}
+#endif
diff --git a/deps/hiredis/adapters/glib.h b/deps/hiredis/adapters/glib.h
new file mode 100644
index 0000000..ad59dd1
--- /dev/null
+++ b/deps/hiredis/adapters/glib.h
@@ -0,0 +1,156 @@
+#ifndef __HIREDIS_GLIB_H__
+#define __HIREDIS_GLIB_H__
+
+#include <glib.h>
+
+#include "../hiredis.h"
+#include "../async.h"
+
+typedef struct
+{
+ GSource source;
+ redisAsyncContext *ac;
+ GPollFD poll_fd;
+} RedisSource;
+
+static void
+redis_source_add_read (gpointer data)
+{
+ RedisSource *source = (RedisSource *)data;
+ g_return_if_fail(source);
+ source->poll_fd.events |= G_IO_IN;
+ g_main_context_wakeup(g_source_get_context((GSource *)data));
+}
+
+static void
+redis_source_del_read (gpointer data)
+{
+ RedisSource *source = (RedisSource *)data;
+ g_return_if_fail(source);
+ source->poll_fd.events &= ~G_IO_IN;
+ g_main_context_wakeup(g_source_get_context((GSource *)data));
+}
+
+static void
+redis_source_add_write (gpointer data)
+{
+ RedisSource *source = (RedisSource *)data;
+ g_return_if_fail(source);
+ source->poll_fd.events |= G_IO_OUT;
+ g_main_context_wakeup(g_source_get_context((GSource *)data));
+}
+
+static void
+redis_source_del_write (gpointer data)
+{
+ RedisSource *source = (RedisSource *)data;
+ g_return_if_fail(source);
+ source->poll_fd.events &= ~G_IO_OUT;
+ g_main_context_wakeup(g_source_get_context((GSource *)data));
+}
+
+static void
+redis_source_cleanup (gpointer data)
+{
+ RedisSource *source = (RedisSource *)data;
+
+ g_return_if_fail(source);
+
+ redis_source_del_read(source);
+ redis_source_del_write(source);
+ /*
+ * It is not our responsibility to remove ourself from the
+ * current main loop. However, we will remove the GPollFD.
+ */
+ if (source->poll_fd.fd >= 0) {
+ g_source_remove_poll((GSource *)data, &source->poll_fd);
+ source->poll_fd.fd = -1;
+ }
+}
+
+static gboolean
+redis_source_prepare (GSource *source,
+ gint *timeout_)
+{
+ RedisSource *redis = (RedisSource *)source;
+ *timeout_ = -1;
+ return !!(redis->poll_fd.events & redis->poll_fd.revents);
+}
+
+static gboolean
+redis_source_check (GSource *source)
+{
+ RedisSource *redis = (RedisSource *)source;
+ return !!(redis->poll_fd.events & redis->poll_fd.revents);
+}
+
+static gboolean
+redis_source_dispatch (GSource *source,
+ GSourceFunc callback,
+ gpointer user_data)
+{
+ RedisSource *redis = (RedisSource *)source;
+
+ if ((redis->poll_fd.revents & G_IO_OUT)) {
+ redisAsyncHandleWrite(redis->ac);
+ redis->poll_fd.revents &= ~G_IO_OUT;
+ }
+
+ if ((redis->poll_fd.revents & G_IO_IN)) {
+ redisAsyncHandleRead(redis->ac);
+ redis->poll_fd.revents &= ~G_IO_IN;
+ }
+
+ if (callback) {
+ return callback(user_data);
+ }
+
+ return TRUE;
+}
+
+static void
+redis_source_finalize (GSource *source)
+{
+ RedisSource *redis = (RedisSource *)source;
+
+ if (redis->poll_fd.fd >= 0) {
+ g_source_remove_poll(source, &redis->poll_fd);
+ redis->poll_fd.fd = -1;
+ }
+}
+
+static GSource *
+redis_source_new (redisAsyncContext *ac)
+{
+ static GSourceFuncs source_funcs = {
+ .prepare = redis_source_prepare,
+ .check = redis_source_check,
+ .dispatch = redis_source_dispatch,
+ .finalize = redis_source_finalize,
+ };
+ redisContext *c = &ac->c;
+ RedisSource *source;
+
+ g_return_val_if_fail(ac != NULL, NULL);
+
+ source = (RedisSource *)g_source_new(&source_funcs, sizeof *source);
+ if (source == NULL)
+ return NULL;
+
+ source->ac = ac;
+ source->poll_fd.fd = c->fd;
+ source->poll_fd.events = 0;
+ source->poll_fd.revents = 0;
+ g_source_add_poll((GSource *)source, &source->poll_fd);
+
+ ac->ev.addRead = redis_source_add_read;
+ ac->ev.delRead = redis_source_del_read;
+ ac->ev.addWrite = redis_source_add_write;
+ ac->ev.delWrite = redis_source_del_write;
+ ac->ev.cleanup = redis_source_cleanup;
+ ac->ev.data = source;
+
+ return (GSource *)source;
+}
+
+#endif /* __HIREDIS_GLIB_H__ */
diff --git a/deps/hiredis/adapters/ivykis.h b/deps/hiredis/adapters/ivykis.h
new file mode 100644
index 0000000..179f6ab
--- /dev/null
+++ b/deps/hiredis/adapters/ivykis.h
@@ -0,0 +1,84 @@
+#ifndef __HIREDIS_IVYKIS_H__
+#define __HIREDIS_IVYKIS_H__
+#include <iv.h>
+#include "../hiredis.h"
+#include "../async.h"
+
+typedef struct redisIvykisEvents {
+ redisAsyncContext *context;
+ struct iv_fd fd;
+} redisIvykisEvents;
+
+static void redisIvykisReadEvent(void *arg) {
+ redisAsyncContext *context = (redisAsyncContext *)arg;
+ redisAsyncHandleRead(context);
+}
+
+static void redisIvykisWriteEvent(void *arg) {
+ redisAsyncContext *context = (redisAsyncContext *)arg;
+ redisAsyncHandleWrite(context);
+}
+
+static void redisIvykisAddRead(void *privdata) {
+ redisIvykisEvents *e = (redisIvykisEvents*)privdata;
+ iv_fd_set_handler_in(&e->fd, redisIvykisReadEvent);
+}
+
+static void redisIvykisDelRead(void *privdata) {
+ redisIvykisEvents *e = (redisIvykisEvents*)privdata;
+ iv_fd_set_handler_in(&e->fd, NULL);
+}
+
+static void redisIvykisAddWrite(void *privdata) {
+ redisIvykisEvents *e = (redisIvykisEvents*)privdata;
+ iv_fd_set_handler_out(&e->fd, redisIvykisWriteEvent);
+}
+
+static void redisIvykisDelWrite(void *privdata) {
+ redisIvykisEvents *e = (redisIvykisEvents*)privdata;
+ iv_fd_set_handler_out(&e->fd, NULL);
+}
+
+static void redisIvykisCleanup(void *privdata) {
+ redisIvykisEvents *e = (redisIvykisEvents*)privdata;
+
+ iv_fd_unregister(&e->fd);
+ hi_free(e);
+}
+
+static int redisIvykisAttach(redisAsyncContext *ac) {
+ redisContext *c = &(ac->c);
+ redisIvykisEvents *e;
+
+ /* Nothing should be attached when something is already attached */
+ if (ac->ev.data != NULL)
+ return REDIS_ERR;
+
+ /* Create container for context and r/w events */
+ e = (redisIvykisEvents*)hi_malloc(sizeof(*e));
+ if (e == NULL)
+ return REDIS_ERR;
+
+ e->context = ac;
+
+ /* Register functions to start/stop listening for events */
+ ac->ev.addRead = redisIvykisAddRead;
+ ac->ev.delRead = redisIvykisDelRead;
+ ac->ev.addWrite = redisIvykisAddWrite;
+ ac->ev.delWrite = redisIvykisDelWrite;
+ ac->ev.cleanup = redisIvykisCleanup;
+ ac->ev.data = e;
+
+ /* Initialize and install read/write events */
+ IV_FD_INIT(&e->fd);
+ e->fd.fd = c->fd;
+ e->fd.handler_in = redisIvykisReadEvent;
+ e->fd.handler_out = redisIvykisWriteEvent;
+ e->fd.handler_err = NULL;
+ e->fd.cookie = e->context;
+
+ iv_fd_register(&e->fd);
+
+ return REDIS_OK;
+}
+#endif
diff --git a/deps/hiredis/adapters/libev.h b/deps/hiredis/adapters/libev.h
new file mode 100644
index 0000000..c59d3da
--- /dev/null
+++ b/deps/hiredis/adapters/libev.h
@@ -0,0 +1,188 @@
+/*
+ * Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com>
+ *
+ * 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.
+ */
+
+#ifndef __HIREDIS_LIBEV_H__
+#define __HIREDIS_LIBEV_H__
+#include <stdlib.h>
+#include <sys/types.h>
+#include <ev.h>
+#include "../hiredis.h"
+#include "../async.h"
+
+typedef struct redisLibevEvents {
+ redisAsyncContext *context;
+ struct ev_loop *loop;
+ int reading, writing;
+ ev_io rev, wev;
+ ev_timer timer;
+} redisLibevEvents;
+
+static void redisLibevReadEvent(EV_P_ ev_io *watcher, int revents) {
+#if EV_MULTIPLICITY
+ ((void)EV_A);
+#endif
+ ((void)revents);
+
+ redisLibevEvents *e = (redisLibevEvents*)watcher->data;
+ redisAsyncHandleRead(e->context);
+}
+
+static void redisLibevWriteEvent(EV_P_ ev_io *watcher, int revents) {
+#if EV_MULTIPLICITY
+ ((void)EV_A);
+#endif
+ ((void)revents);
+
+ redisLibevEvents *e = (redisLibevEvents*)watcher->data;
+ redisAsyncHandleWrite(e->context);
+}
+
+static void redisLibevAddRead(void *privdata) {
+ redisLibevEvents *e = (redisLibevEvents*)privdata;
+#if EV_MULTIPLICITY
+ struct ev_loop *loop = e->loop;
+#endif
+ if (!e->reading) {
+ e->reading = 1;
+ ev_io_start(EV_A_ &e->rev);
+ }
+}
+
+static void redisLibevDelRead(void *privdata) {
+ redisLibevEvents *e = (redisLibevEvents*)privdata;
+#if EV_MULTIPLICITY
+ struct ev_loop *loop = e->loop;
+#endif
+ if (e->reading) {
+ e->reading = 0;
+ ev_io_stop(EV_A_ &e->rev);
+ }
+}
+
+static void redisLibevAddWrite(void *privdata) {
+ redisLibevEvents *e = (redisLibevEvents*)privdata;
+#if EV_MULTIPLICITY
+ struct ev_loop *loop = e->loop;
+#endif
+ if (!e->writing) {
+ e->writing = 1;
+ ev_io_start(EV_A_ &e->wev);
+ }
+}
+
+static void redisLibevDelWrite(void *privdata) {
+ redisLibevEvents *e = (redisLibevEvents*)privdata;
+#if EV_MULTIPLICITY
+ struct ev_loop *loop = e->loop;
+#endif
+ if (e->writing) {
+ e->writing = 0;
+ ev_io_stop(EV_A_ &e->wev);
+ }
+}
+
+static void redisLibevStopTimer(void *privdata) {
+ redisLibevEvents *e = (redisLibevEvents*)privdata;
+#if EV_MULTIPLICITY
+ struct ev_loop *loop = e->loop;
+#endif
+ ev_timer_stop(EV_A_ &e->timer);
+}
+
+static void redisLibevCleanup(void *privdata) {
+ redisLibevEvents *e = (redisLibevEvents*)privdata;
+ redisLibevDelRead(privdata);
+ redisLibevDelWrite(privdata);
+ redisLibevStopTimer(privdata);
+ hi_free(e);
+}
+
+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);
+}
+
+static void redisLibevSetTimeout(void *privdata, struct timeval tv) {
+ redisLibevEvents *e = (redisLibevEvents*)privdata;
+#if EV_MULTIPLICITY
+ struct ev_loop *loop = e->loop;
+#endif
+
+ if (!ev_is_active(&e->timer)) {
+ ev_init(&e->timer, redisLibevTimeout);
+ e->timer.data = e;
+ }
+
+ e->timer.repeat = tv.tv_sec + tv.tv_usec / 1000000.00;
+ ev_timer_again(EV_A_ &e->timer);
+}
+
+static int redisLibevAttach(EV_P_ redisAsyncContext *ac) {
+ redisContext *c = &(ac->c);
+ redisLibevEvents *e;
+
+ /* Nothing should be attached when something is already attached */
+ if (ac->ev.data != NULL)
+ return REDIS_ERR;
+
+ /* Create container for context and r/w events */
+ e = (redisLibevEvents*)hi_calloc(1, sizeof(*e));
+ if (e == NULL)
+ return REDIS_ERR;
+
+ e->context = ac;
+#if EV_MULTIPLICITY
+ e->loop = EV_A;
+#else
+ e->loop = NULL;
+#endif
+ e->rev.data = e;
+ e->wev.data = e;
+
+ /* Register functions to start/stop listening for events */
+ ac->ev.addRead = redisLibevAddRead;
+ ac->ev.delRead = redisLibevDelRead;
+ ac->ev.addWrite = redisLibevAddWrite;
+ ac->ev.delWrite = redisLibevDelWrite;
+ ac->ev.cleanup = redisLibevCleanup;
+ ac->ev.scheduleTimer = redisLibevSetTimeout;
+ ac->ev.data = e;
+
+ /* Initialize read/write events */
+ ev_io_init(&e->rev,redisLibevReadEvent,c->fd,EV_READ);
+ ev_io_init(&e->wev,redisLibevWriteEvent,c->fd,EV_WRITE);
+ return REDIS_OK;
+}
+
+#endif
diff --git a/deps/hiredis/adapters/libevent.h b/deps/hiredis/adapters/libevent.h
new file mode 100644
index 0000000..73bb8ed
--- /dev/null
+++ b/deps/hiredis/adapters/libevent.h
@@ -0,0 +1,175 @@
+/*
+ * Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com>
+ *
+ * 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.
+ */
+
+#ifndef __HIREDIS_LIBEVENT_H__
+#define __HIREDIS_LIBEVENT_H__
+#include <event2/event.h>
+#include "../hiredis.h"
+#include "../async.h"
+
+#define REDIS_LIBEVENT_DELETED 0x01
+#define REDIS_LIBEVENT_ENTERED 0x02
+
+typedef struct redisLibeventEvents {
+ redisAsyncContext *context;
+ struct event *ev;
+ struct event_base *base;
+ struct timeval tv;
+ short flags;
+ short state;
+} redisLibeventEvents;
+
+static void redisLibeventDestroy(redisLibeventEvents *e) {
+ hi_free(e);
+}
+
+static void redisLibeventHandler(evutil_socket_t fd, short event, void *arg) {
+ ((void)fd);
+ redisLibeventEvents *e = (redisLibeventEvents*)arg;
+ e->state |= REDIS_LIBEVENT_ENTERED;
+
+ #define CHECK_DELETED() if (e->state & REDIS_LIBEVENT_DELETED) {\
+ redisLibeventDestroy(e);\
+ return; \
+ }
+
+ if ((event & EV_TIMEOUT) && (e->state & REDIS_LIBEVENT_DELETED) == 0) {
+ redisAsyncHandleTimeout(e->context);
+ CHECK_DELETED();
+ }
+
+ if ((event & EV_READ) && e->context && (e->state & REDIS_LIBEVENT_DELETED) == 0) {
+ redisAsyncHandleRead(e->context);
+ CHECK_DELETED();
+ }
+
+ if ((event & EV_WRITE) && e->context && (e->state & REDIS_LIBEVENT_DELETED) == 0) {
+ redisAsyncHandleWrite(e->context);
+ CHECK_DELETED();
+ }
+
+ e->state &= ~REDIS_LIBEVENT_ENTERED;
+ #undef CHECK_DELETED
+}
+
+static void redisLibeventUpdate(void *privdata, short flag, int isRemove) {
+ redisLibeventEvents *e = (redisLibeventEvents *)privdata;
+ const struct timeval *tv = e->tv.tv_sec || e->tv.tv_usec ? &e->tv : NULL;
+
+ if (isRemove) {
+ if ((e->flags & flag) == 0) {
+ return;
+ } else {
+ e->flags &= ~flag;
+ }
+ } else {
+ if (e->flags & flag) {
+ return;
+ } else {
+ e->flags |= flag;
+ }
+ }
+
+ event_del(e->ev);
+ event_assign(e->ev, e->base, e->context->c.fd, e->flags | EV_PERSIST,
+ redisLibeventHandler, privdata);
+ event_add(e->ev, tv);
+}
+
+static void redisLibeventAddRead(void *privdata) {
+ redisLibeventUpdate(privdata, EV_READ, 0);
+}
+
+static void redisLibeventDelRead(void *privdata) {
+ redisLibeventUpdate(privdata, EV_READ, 1);
+}
+
+static void redisLibeventAddWrite(void *privdata) {
+ redisLibeventUpdate(privdata, EV_WRITE, 0);
+}
+
+static void redisLibeventDelWrite(void *privdata) {
+ redisLibeventUpdate(privdata, EV_WRITE, 1);
+}
+
+static void redisLibeventCleanup(void *privdata) {
+ redisLibeventEvents *e = (redisLibeventEvents*)privdata;
+ if (!e) {
+ return;
+ }
+ event_del(e->ev);
+ event_free(e->ev);
+ e->ev = NULL;
+
+ if (e->state & REDIS_LIBEVENT_ENTERED) {
+ e->state |= REDIS_LIBEVENT_DELETED;
+ } else {
+ redisLibeventDestroy(e);
+ }
+}
+
+static void redisLibeventSetTimeout(void *privdata, struct timeval tv) {
+ redisLibeventEvents *e = (redisLibeventEvents *)privdata;
+ short flags = e->flags;
+ e->flags = 0;
+ e->tv = tv;
+ redisLibeventUpdate(e, flags, 0);
+}
+
+static int redisLibeventAttach(redisAsyncContext *ac, struct event_base *base) {
+ redisContext *c = &(ac->c);
+ redisLibeventEvents *e;
+
+ /* Nothing should be attached when something is already attached */
+ if (ac->ev.data != NULL)
+ return REDIS_ERR;
+
+ /* Create container for context and r/w events */
+ e = (redisLibeventEvents*)hi_calloc(1, sizeof(*e));
+ if (e == NULL)
+ return REDIS_ERR;
+
+ e->context = ac;
+
+ /* Register functions to start/stop listening for events */
+ ac->ev.addRead = redisLibeventAddRead;
+ ac->ev.delRead = redisLibeventDelRead;
+ ac->ev.addWrite = redisLibeventAddWrite;
+ ac->ev.delWrite = redisLibeventDelWrite;
+ ac->ev.cleanup = redisLibeventCleanup;
+ ac->ev.scheduleTimer = redisLibeventSetTimeout;
+ ac->ev.data = e;
+
+ /* Initialize and install read/write events */
+ e->ev = event_new(base, c->fd, EV_READ | EV_WRITE, redisLibeventHandler, e);
+ e->base = base;
+ return REDIS_OK;
+}
+#endif
diff --git a/deps/hiredis/adapters/libuv.h b/deps/hiredis/adapters/libuv.h
new file mode 100644
index 0000000..df0a845
--- /dev/null
+++ b/deps/hiredis/adapters/libuv.h
@@ -0,0 +1,163 @@
+#ifndef __HIREDIS_LIBUV_H__
+#define __HIREDIS_LIBUV_H__
+#include <stdlib.h>
+#include <uv.h>
+#include "../hiredis.h"
+#include "../async.h"
+#include <string.h>
+
+typedef struct redisLibuvEvents {
+ 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);
+ }
+}
+
+
+static void redisLibuvAddRead(void *privdata) {
+ redisLibuvEvents* p = (redisLibuvEvents*)privdata;
+
+ p->events |= UV_READABLE;
+
+ uv_poll_start(&p->handle, p->events, redisLibuvPoll);
+}
+
+
+static void redisLibuvDelRead(void *privdata) {
+ redisLibuvEvents* p = (redisLibuvEvents*)privdata;
+
+ p->events &= ~UV_READABLE;
+
+ 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;
+
+ p->events |= UV_WRITABLE;
+
+ uv_poll_start(&p->handle, p->events, redisLibuvPoll);
+}
+
+
+static void redisLibuvDelWrite(void *privdata) {
+ redisLibuvEvents* p = (redisLibuvEvents*)privdata;
+
+ p->events &= ~UV_WRITABLE;
+
+ 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_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`
+}
+
+// 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;
+
+ 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);
+
+ 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.scheduleTimer = redisLibuvSetTimeout;
+
+ redisLibuvEvents* p = (redisLibuvEvents*)hi_malloc(sizeof(*p));
+ if (p == NULL)
+ return REDIS_ERR;
+
+ memset(p, 0, sizeof(*p));
+
+ 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;
+
+ return REDIS_OK;
+}
+#endif
diff --git a/deps/hiredis/adapters/macosx.h b/deps/hiredis/adapters/macosx.h
new file mode 100644
index 0000000..3c87f1b
--- /dev/null
+++ b/deps/hiredis/adapters/macosx.h
@@ -0,0 +1,115 @@
+//
+// Created by Дмитрий Бахвалов on 13.07.15.
+// Copyright (c) 2015 Dmitry Bakhvalov. All rights reserved.
+//
+
+#ifndef __HIREDIS_MACOSX_H__
+#define __HIREDIS_MACOSX_H__
+
+#include <CoreFoundation/CoreFoundation.h>
+
+#include "../hiredis.h"
+#include "../async.h"
+
+typedef struct {
+ redisAsyncContext *context;
+ CFSocketRef socketRef;
+ CFRunLoopSourceRef sourceRef;
+} RedisRunLoop;
+
+static int freeRedisRunLoop(RedisRunLoop* redisRunLoop) {
+ if( redisRunLoop != NULL ) {
+ if( redisRunLoop->sourceRef != NULL ) {
+ CFRunLoopSourceInvalidate(redisRunLoop->sourceRef);
+ CFRelease(redisRunLoop->sourceRef);
+ }
+ if( redisRunLoop->socketRef != NULL ) {
+ CFSocketInvalidate(redisRunLoop->socketRef);
+ CFRelease(redisRunLoop->socketRef);
+ }
+ hi_free(redisRunLoop);
+ }
+ return REDIS_ERR;
+}
+
+static void redisMacOSAddRead(void *privdata) {
+ RedisRunLoop *redisRunLoop = (RedisRunLoop*)privdata;
+ CFSocketEnableCallBacks(redisRunLoop->socketRef, kCFSocketReadCallBack);
+}
+
+static void redisMacOSDelRead(void *privdata) {
+ RedisRunLoop *redisRunLoop = (RedisRunLoop*)privdata;
+ CFSocketDisableCallBacks(redisRunLoop->socketRef, kCFSocketReadCallBack);
+}
+
+static void redisMacOSAddWrite(void *privdata) {
+ RedisRunLoop *redisRunLoop = (RedisRunLoop*)privdata;
+ CFSocketEnableCallBacks(redisRunLoop->socketRef, kCFSocketWriteCallBack);
+}
+
+static void redisMacOSDelWrite(void *privdata) {
+ RedisRunLoop *redisRunLoop = (RedisRunLoop*)privdata;
+ CFSocketDisableCallBacks(redisRunLoop->socketRef, kCFSocketWriteCallBack);
+}
+
+static void redisMacOSCleanup(void *privdata) {
+ RedisRunLoop *redisRunLoop = (RedisRunLoop*)privdata;
+ freeRedisRunLoop(redisRunLoop);
+}
+
+static void redisMacOSAsyncCallback(CFSocketRef __unused s, CFSocketCallBackType callbackType, CFDataRef __unused address, const void __unused *data, void *info) {
+ redisAsyncContext* context = (redisAsyncContext*) info;
+
+ switch (callbackType) {
+ case kCFSocketReadCallBack:
+ redisAsyncHandleRead(context);
+ break;
+
+ case kCFSocketWriteCallBack:
+ redisAsyncHandleWrite(context);
+ break;
+
+ default:
+ break;
+ }
+}
+
+static int redisMacOSAttach(redisAsyncContext *redisAsyncCtx, CFRunLoopRef runLoop) {
+ redisContext *redisCtx = &(redisAsyncCtx->c);
+
+ /* Nothing should be attached when something is already attached */
+ if( redisAsyncCtx->ev.data != NULL ) return REDIS_ERR;
+
+ RedisRunLoop* redisRunLoop = (RedisRunLoop*) hi_calloc(1, sizeof(RedisRunLoop));
+ if (redisRunLoop == NULL)
+ return REDIS_ERR;
+
+ /* Setup redis stuff */
+ redisRunLoop->context = redisAsyncCtx;
+
+ redisAsyncCtx->ev.addRead = redisMacOSAddRead;
+ redisAsyncCtx->ev.delRead = redisMacOSDelRead;
+ redisAsyncCtx->ev.addWrite = redisMacOSAddWrite;
+ redisAsyncCtx->ev.delWrite = redisMacOSDelWrite;
+ redisAsyncCtx->ev.cleanup = redisMacOSCleanup;
+ redisAsyncCtx->ev.data = redisRunLoop;
+
+ /* Initialize and install read/write events */
+ CFSocketContext socketCtx = { 0, redisAsyncCtx, NULL, NULL, NULL };
+
+ redisRunLoop->socketRef = CFSocketCreateWithNative(NULL, redisCtx->fd,
+ kCFSocketReadCallBack | kCFSocketWriteCallBack,
+ redisMacOSAsyncCallback,
+ &socketCtx);
+ if( !redisRunLoop->socketRef ) return freeRedisRunLoop(redisRunLoop);
+
+ redisRunLoop->sourceRef = CFSocketCreateRunLoopSource(NULL, redisRunLoop->socketRef, 0);
+ if( !redisRunLoop->sourceRef ) return freeRedisRunLoop(redisRunLoop);
+
+ CFRunLoopAddSource(runLoop, redisRunLoop->sourceRef, kCFRunLoopDefaultMode);
+
+ return REDIS_OK;
+}
+
+#endif
+
diff --git a/deps/hiredis/adapters/qt.h b/deps/hiredis/adapters/qt.h
new file mode 100644
index 0000000..5cc02e6
--- /dev/null
+++ b/deps/hiredis/adapters/qt.h
@@ -0,0 +1,135 @@
+/*-
+ * Copyright (C) 2014 Pietro Cerutti <gahr@gahr.ch>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY AUTHOR 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 AUTHOR 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.
+ */
+
+#ifndef __HIREDIS_QT_H__
+#define __HIREDIS_QT_H__
+#include <QSocketNotifier>
+#include "../async.h"
+
+static void RedisQtAddRead(void *);
+static void RedisQtDelRead(void *);
+static void RedisQtAddWrite(void *);
+static void RedisQtDelWrite(void *);
+static void RedisQtCleanup(void *);
+
+class RedisQtAdapter : public QObject {
+
+ Q_OBJECT
+
+ friend
+ void RedisQtAddRead(void * adapter) {
+ RedisQtAdapter * a = static_cast<RedisQtAdapter *>(adapter);
+ a->addRead();
+ }
+
+ friend
+ void RedisQtDelRead(void * adapter) {
+ RedisQtAdapter * a = static_cast<RedisQtAdapter *>(adapter);
+ a->delRead();
+ }
+
+ friend
+ void RedisQtAddWrite(void * adapter) {
+ RedisQtAdapter * a = static_cast<RedisQtAdapter *>(adapter);
+ a->addWrite();
+ }
+
+ friend
+ void RedisQtDelWrite(void * adapter) {
+ RedisQtAdapter * a = static_cast<RedisQtAdapter *>(adapter);
+ a->delWrite();
+ }
+
+ friend
+ void RedisQtCleanup(void * adapter) {
+ RedisQtAdapter * a = static_cast<RedisQtAdapter *>(adapter);
+ a->cleanup();
+ }
+
+ public:
+ RedisQtAdapter(QObject * parent = 0)
+ : QObject(parent), m_ctx(0), m_read(0), m_write(0) { }
+
+ ~RedisQtAdapter() {
+ if (m_ctx != 0) {
+ m_ctx->ev.data = NULL;
+ }
+ }
+
+ int setContext(redisAsyncContext * ac) {
+ if (ac->ev.data != NULL) {
+ return REDIS_ERR;
+ }
+ m_ctx = ac;
+ m_ctx->ev.data = this;
+ m_ctx->ev.addRead = RedisQtAddRead;
+ m_ctx->ev.delRead = RedisQtDelRead;
+ m_ctx->ev.addWrite = RedisQtAddWrite;
+ m_ctx->ev.delWrite = RedisQtDelWrite;
+ m_ctx->ev.cleanup = RedisQtCleanup;
+ return REDIS_OK;
+ }
+
+ private:
+ void addRead() {
+ if (m_read) return;
+ m_read = new QSocketNotifier(m_ctx->c.fd, QSocketNotifier::Read, 0);
+ connect(m_read, SIGNAL(activated(int)), this, SLOT(read()));
+ }
+
+ void delRead() {
+ if (!m_read) return;
+ delete m_read;
+ m_read = 0;
+ }
+
+ void addWrite() {
+ if (m_write) return;
+ m_write = new QSocketNotifier(m_ctx->c.fd, QSocketNotifier::Write, 0);
+ connect(m_write, SIGNAL(activated(int)), this, SLOT(write()));
+ }
+
+ void delWrite() {
+ if (!m_write) return;
+ delete m_write;
+ m_write = 0;
+ }
+
+ void cleanup() {
+ delRead();
+ delWrite();
+ }
+
+ private slots:
+ void read() { redisAsyncHandleRead(m_ctx); }
+ void write() { redisAsyncHandleWrite(m_ctx); }
+
+ private:
+ redisAsyncContext * m_ctx;
+ QSocketNotifier * m_read;
+ QSocketNotifier * m_write;
+};
+
+#endif /* !__HIREDIS_QT_H__ */
diff --git a/deps/hiredis/alloc.c b/deps/hiredis/alloc.c
new file mode 100644
index 0000000..0902286
--- /dev/null
+++ b/deps/hiredis/alloc.c
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2020, Michael Grunder <michael dot grunder at gmail dot com>
+ *
+ * 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 "fmacros.h"
+#include "alloc.h"
+#include <string.h>
+#include <stdlib.h>
+
+hiredisAllocFuncs hiredisAllocFns = {
+ .mallocFn = malloc,
+ .callocFn = calloc,
+ .reallocFn = realloc,
+ .strdupFn = strdup,
+ .freeFn = free,
+};
+
+/* Override hiredis' allocators with ones supplied by the user */
+hiredisAllocFuncs hiredisSetAllocators(hiredisAllocFuncs *override) {
+ hiredisAllocFuncs orig = hiredisAllocFns;
+
+ hiredisAllocFns = *override;
+
+ return orig;
+}
+
+/* Reset allocators to use libc defaults */
+void hiredisResetAllocators(void) {
+ hiredisAllocFns = (hiredisAllocFuncs) {
+ .mallocFn = malloc,
+ .callocFn = calloc,
+ .reallocFn = realloc,
+ .strdupFn = strdup,
+ .freeFn = free,
+ };
+}
+
+#ifdef _WIN32
+
+void *hi_malloc(size_t size) {
+ return hiredisAllocFns.mallocFn(size);
+}
+
+void *hi_calloc(size_t nmemb, size_t size) {
+ /* Overflow check as the user can specify any arbitrary allocator */
+ if (SIZE_MAX / size < nmemb)
+ return NULL;
+
+ return hiredisAllocFns.callocFn(nmemb, size);
+}
+
+void *hi_realloc(void *ptr, size_t size) {
+ return hiredisAllocFns.reallocFn(ptr, size);
+}
+
+char *hi_strdup(const char *str) {
+ return hiredisAllocFns.strdupFn(str);
+}
+
+void hi_free(void *ptr) {
+ hiredisAllocFns.freeFn(ptr);
+}
+
+#endif
diff --git a/deps/hiredis/alloc.h b/deps/hiredis/alloc.h
new file mode 100644
index 0000000..771f9fe
--- /dev/null
+++ b/deps/hiredis/alloc.h
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2020, Michael Grunder <michael dot grunder at gmail dot com>
+ *
+ * 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.
+ */
+
+#ifndef HIREDIS_ALLOC_H
+#define HIREDIS_ALLOC_H
+
+#include <stddef.h> /* for size_t */
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Structure pointing to our actually configured allocators */
+typedef struct hiredisAllocFuncs {
+ void *(*mallocFn)(size_t);
+ void *(*callocFn)(size_t,size_t);
+ void *(*reallocFn)(void*,size_t);
+ char *(*strdupFn)(const char*);
+ void (*freeFn)(void*);
+} hiredisAllocFuncs;
+
+hiredisAllocFuncs hiredisSetAllocators(hiredisAllocFuncs *ha);
+void hiredisResetAllocators(void);
+
+#ifndef _WIN32
+
+/* Hiredis' configured allocator function pointer struct */
+extern hiredisAllocFuncs hiredisAllocFns;
+
+static inline void *hi_malloc(size_t size) {
+ return hiredisAllocFns.mallocFn(size);
+}
+
+static inline void *hi_calloc(size_t nmemb, size_t size) {
+ /* Overflow check as the user can specify any arbitrary allocator */
+ if (SIZE_MAX / size < nmemb)
+ return NULL;
+
+ return hiredisAllocFns.callocFn(nmemb, size);
+}
+
+static inline void *hi_realloc(void *ptr, size_t size) {
+ return hiredisAllocFns.reallocFn(ptr, size);
+}
+
+static inline char *hi_strdup(const char *str) {
+ return hiredisAllocFns.strdupFn(str);
+}
+
+static inline void hi_free(void *ptr) {
+ hiredisAllocFns.freeFn(ptr);
+}
+
+#else
+
+void *hi_malloc(size_t size);
+void *hi_calloc(size_t nmemb, size_t size);
+void *hi_realloc(void *ptr, size_t size);
+char *hi_strdup(const char *str);
+void hi_free(void *ptr);
+
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* HIREDIS_ALLOC_H */
diff --git a/deps/hiredis/appveyor.yml b/deps/hiredis/appveyor.yml
new file mode 100644
index 0000000..5b43fdb
--- /dev/null
+++ b/deps/hiredis/appveyor.yml
@@ -0,0 +1,24 @@
+# Appveyor configuration file for CI build of hiredis on Windows (under Cygwin)
+environment:
+ matrix:
+ - CYG_BASH: C:\cygwin64\bin\bash
+ CC: gcc
+ - CYG_BASH: C:\cygwin\bin\bash
+ CC: gcc
+ CFLAGS: -m32
+ CXXFLAGS: -m32
+ LDFLAGS: -m32
+
+clone_depth: 1
+
+# Attempt to ensure we don't try to convert line endings to Win32 CRLF as this will cause build to fail
+init:
+ - git config --global core.autocrlf input
+
+# Install needed build dependencies
+install:
+ - '%CYG_BASH% -lc "cygcheck -dc cygwin"'
+
+build_script:
+ - 'echo building...'
+ - '%CYG_BASH% -lc "cd $APPVEYOR_BUILD_FOLDER; exec 0</dev/null; mkdir build && cd build && cmake .. -G \"Unix Makefiles\" && make VERBOSE=1"'
diff --git a/deps/hiredis/async.c b/deps/hiredis/async.c
new file mode 100644
index 0000000..d73d09f
--- /dev/null
+++ b/deps/hiredis/async.c
@@ -0,0 +1,909 @@
+/*
+ * Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com>
+ * Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com>
+ *
+ * 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 "fmacros.h"
+#include "alloc.h"
+#include <stdlib.h>
+#include <string.h>
+#ifndef _MSC_VER
+#include <strings.h>
+#endif
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include "async.h"
+#include "net.h"
+#include "dict.c"
+#include "sds.h"
+#include "win32.h"
+
+#include "async_private.h"
+
+#ifdef NDEBUG
+#undef assert
+#define assert(e) (void)(e)
+#endif
+
+/* Forward declarations of hiredis.c functions */
+int __redisAppendCommand(redisContext *c, const char *cmd, size_t len);
+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));
+}
+
+static void *callbackValDup(void *privdata, const void *src) {
+ ((void) privdata);
+ redisCallback *dup;
+
+ dup = hi_malloc(sizeof(*dup));
+ if (dup == NULL)
+ return NULL;
+
+ memcpy(dup,src,sizeof(*dup));
+ return dup;
+}
+
+static int callbackKeyCompare(void *privdata, const void *key1, const void *key2) {
+ int l1, l2;
+ ((void) privdata);
+
+ l1 = hi_sdslen((const hisds)key1);
+ l2 = hi_sdslen((const hisds)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);
+}
+
+static void callbackValDestructor(void *privdata, void *val) {
+ ((void) privdata);
+ hi_free(val);
+}
+
+static dictType callbackDict = {
+ callbackHash,
+ NULL,
+ callbackValDup,
+ callbackKeyCompare,
+ callbackKeyDestructor,
+ callbackValDestructor
+};
+
+static redisAsyncContext *redisAsyncInitialize(redisContext *c) {
+ redisAsyncContext *ac;
+ dict *channels = NULL, *patterns = NULL;
+
+ channels = dictCreate(&callbackDict,NULL);
+ if (channels == NULL)
+ goto oom;
+
+ patterns = dictCreate(&callbackDict,NULL);
+ if (patterns == NULL)
+ goto oom;
+
+ ac = hi_realloc(c,sizeof(redisAsyncContext));
+ if (ac == NULL)
+ goto oom;
+
+ c = &(ac->c);
+
+ /* The regular connect functions will always set the flag REDIS_CONNECTED.
+ * For the async API, we want to wait until the first write event is
+ * received up before setting this flag, so reset it here. */
+ c->flags &= ~REDIS_CONNECTED;
+
+ ac->err = 0;
+ ac->errstr = NULL;
+ ac->data = NULL;
+ ac->dataCleanup = NULL;
+
+ ac->ev.data = NULL;
+ ac->ev.addRead = NULL;
+ ac->ev.delRead = NULL;
+ ac->ev.addWrite = NULL;
+ ac->ev.delWrite = NULL;
+ ac->ev.cleanup = NULL;
+ ac->ev.scheduleTimer = NULL;
+
+ ac->onConnect = NULL;
+ ac->onDisconnect = NULL;
+
+ ac->replies.head = NULL;
+ ac->replies.tail = NULL;
+ ac->sub.replies.head = NULL;
+ ac->sub.replies.tail = NULL;
+ ac->sub.channels = channels;
+ ac->sub.patterns = patterns;
+
+ return ac;
+oom:
+ if (channels) dictRelease(channels);
+ if (patterns) dictRelease(patterns);
+ return NULL;
+}
+
+/* We want the error field to be accessible directly instead of requiring
+ * an indirection to the redisContext struct. */
+static void __redisAsyncCopyError(redisAsyncContext *ac) {
+ if (!ac)
+ return;
+
+ redisContext *c = &(ac->c);
+ ac->err = c->err;
+ ac->errstr = c->errstr;
+}
+
+redisAsyncContext *redisAsyncConnectWithOptions(const redisOptions *options) {
+ redisOptions myOptions = *options;
+ redisContext *c;
+ redisAsyncContext *ac;
+
+ /* Clear any erroneously set sync callback and flag that we don't want to
+ * use freeReplyObject by default. */
+ myOptions.push_cb = NULL;
+ myOptions.options |= REDIS_OPT_NO_PUSH_AUTOFREE;
+
+ myOptions.options |= REDIS_OPT_NONBLOCK;
+ c = redisConnectWithOptions(&myOptions);
+ if (c == NULL) {
+ return NULL;
+ }
+
+ ac = redisAsyncInitialize(c);
+ if (ac == NULL) {
+ redisFree(c);
+ return NULL;
+ }
+
+ /* Set any configured async push handler */
+ redisAsyncSetPushCallback(ac, myOptions.async_push_cb);
+
+ __redisAsyncCopyError(ac);
+ return ac;
+}
+
+redisAsyncContext *redisAsyncConnect(const char *ip, int port) {
+ redisOptions options = {0};
+ REDIS_OPTIONS_SET_TCP(&options, ip, port);
+ return redisAsyncConnectWithOptions(&options);
+}
+
+redisAsyncContext *redisAsyncConnectBind(const char *ip, int port,
+ const char *source_addr) {
+ redisOptions options = {0};
+ REDIS_OPTIONS_SET_TCP(&options, ip, port);
+ options.endpoint.tcp.source_addr = source_addr;
+ return redisAsyncConnectWithOptions(&options);
+}
+
+redisAsyncContext *redisAsyncConnectBindWithReuse(const char *ip, int port,
+ const char *source_addr) {
+ redisOptions options = {0};
+ REDIS_OPTIONS_SET_TCP(&options, ip, port);
+ options.options |= REDIS_OPT_REUSEADDR;
+ options.endpoint.tcp.source_addr = source_addr;
+ return redisAsyncConnectWithOptions(&options);
+}
+
+redisAsyncContext *redisAsyncConnectUnix(const char *path) {
+ redisOptions options = {0};
+ REDIS_OPTIONS_SET_UNIX(&options, path);
+ return redisAsyncConnectWithOptions(&options);
+}
+
+int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn) {
+ if (ac->onConnect == NULL) {
+ ac->onConnect = fn;
+
+ /* The common way to detect an established connection is to wait for
+ * the first write event to be fired. This assumes the related event
+ * library functions are already set. */
+ _EL_ADD_WRITE(ac);
+ return REDIS_OK;
+ }
+ return REDIS_ERR;
+}
+
+int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn) {
+ if (ac->onDisconnect == NULL) {
+ ac->onDisconnect = fn;
+ return REDIS_OK;
+ }
+ return REDIS_ERR;
+}
+
+/* Helper functions to push/shift callbacks */
+static int __redisPushCallback(redisCallbackList *list, redisCallback *source) {
+ redisCallback *cb;
+
+ /* Copy callback from stack to heap */
+ cb = hi_malloc(sizeof(*cb));
+ if (cb == NULL)
+ return REDIS_ERR_OOM;
+
+ if (source != NULL) {
+ memcpy(cb,source,sizeof(*cb));
+ cb->next = NULL;
+ }
+
+ /* Store callback in list */
+ if (list->head == NULL)
+ list->head = cb;
+ if (list->tail != NULL)
+ list->tail->next = cb;
+ list->tail = cb;
+ return REDIS_OK;
+}
+
+static int __redisShiftCallback(redisCallbackList *list, redisCallback *target) {
+ redisCallback *cb = list->head;
+ if (cb != NULL) {
+ list->head = cb->next;
+ if (cb == list->tail)
+ list->tail = NULL;
+
+ /* Copy callback from heap to stack */
+ if (target != NULL)
+ memcpy(target,cb,sizeof(*cb));
+ hi_free(cb);
+ return REDIS_OK;
+ }
+ return REDIS_ERR;
+}
+
+static void __redisRunCallback(redisAsyncContext *ac, redisCallback *cb, redisReply *reply) {
+ redisContext *c = &(ac->c);
+ if (cb->fn != NULL) {
+ c->flags |= REDIS_IN_CALLBACK;
+ cb->fn(ac,reply,cb->privdata);
+ c->flags &= ~REDIS_IN_CALLBACK;
+ }
+}
+
+static void __redisRunPushCallback(redisAsyncContext *ac, redisReply *reply) {
+ if (ac->push_cb != NULL) {
+ ac->c.flags |= REDIS_IN_CALLBACK;
+ ac->push_cb(ac, reply);
+ ac->c.flags &= ~REDIS_IN_CALLBACK;
+ }
+}
+
+/* Helper function to free the context. */
+static void __redisAsyncFree(redisAsyncContext *ac) {
+ redisContext *c = &(ac->c);
+ redisCallback cb;
+ dictIterator it;
+ dictEntry *de;
+
+ /* Execute pending callbacks with NULL reply. */
+ while (__redisShiftCallback(&ac->replies,&cb) == REDIS_OK)
+ __redisRunCallback(ac,&cb,NULL);
+ while (__redisShiftCallback(&ac->sub.replies,&cb) == REDIS_OK)
+ __redisRunCallback(ac,&cb,NULL);
+
+ /* Run subscription callbacks with NULL reply */
+ if (ac->sub.channels) {
+ dictInitIterator(&it,ac->sub.channels);
+ while ((de = dictNext(&it)) != NULL)
+ __redisRunCallback(ac,dictGetEntryVal(de),NULL);
+
+ dictRelease(ac->sub.channels);
+ }
+
+ if (ac->sub.patterns) {
+ dictInitIterator(&it,ac->sub.patterns);
+ while ((de = dictNext(&it)) != NULL)
+ __redisRunCallback(ac,dictGetEntryVal(de),NULL);
+
+ dictRelease(ac->sub.patterns);
+ }
+
+ /* Signal event lib to clean up */
+ _EL_CLEANUP(ac);
+
+ /* Execute disconnect callback. When redisAsyncFree() initiated destroying
+ * this context, the status will always be REDIS_OK. */
+ if (ac->onDisconnect && (c->flags & REDIS_CONNECTED)) {
+ if (c->flags & REDIS_FREEING) {
+ ac->onDisconnect(ac,REDIS_OK);
+ } else {
+ ac->onDisconnect(ac,(ac->err == 0) ? REDIS_OK : REDIS_ERR);
+ }
+ }
+
+ if (ac->dataCleanup) {
+ ac->dataCleanup(ac->data);
+ }
+
+ /* Cleanup self */
+ redisFree(c);
+}
+
+/* Free the async context. When this function is called from a callback,
+ * control needs to be returned to redisProcessCallbacks() before actual
+ * free'ing. To do so, a flag is set on the context which is picked up by
+ * redisProcessCallbacks(). Otherwise, the context is immediately free'd. */
+void redisAsyncFree(redisAsyncContext *ac) {
+ redisContext *c = &(ac->c);
+ c->flags |= REDIS_FREEING;
+ if (!(c->flags & REDIS_IN_CALLBACK))
+ __redisAsyncFree(ac);
+}
+
+/* Helper function to make the disconnect happen and clean up. */
+void __redisAsyncDisconnect(redisAsyncContext *ac) {
+ redisContext *c = &(ac->c);
+
+ /* Make sure error is accessible if there is any */
+ __redisAsyncCopyError(ac);
+
+ if (ac->err == 0) {
+ /* For clean disconnects, there should be no pending callbacks. */
+ int ret = __redisShiftCallback(&ac->replies,NULL);
+ assert(ret == REDIS_ERR);
+ } else {
+ /* Disconnection is caused by an error, make sure that pending
+ * callbacks cannot call new commands. */
+ c->flags |= REDIS_DISCONNECTING;
+ }
+
+ /* cleanup event library on disconnect.
+ * this is safe to call multiple times */
+ _EL_CLEANUP(ac);
+
+ /* For non-clean disconnects, __redisAsyncFree() will execute pending
+ * callbacks with a NULL-reply. */
+ if (!(c->flags & REDIS_NO_AUTO_FREE)) {
+ __redisAsyncFree(ac);
+ }
+}
+
+/* Tries to do a clean disconnect from Redis, meaning it stops new commands
+ * from being issued, but tries to flush the output buffer and execute
+ * callbacks for all remaining replies. When this function is called from a
+ * callback, there might be more replies and we can safely defer disconnecting
+ * to redisProcessCallbacks(). Otherwise, we can only disconnect immediately
+ * when there are no pending callbacks. */
+void redisAsyncDisconnect(redisAsyncContext *ac) {
+ redisContext *c = &(ac->c);
+ c->flags |= REDIS_DISCONNECTING;
+
+ /** unset the auto-free flag here, because disconnect undoes this */
+ c->flags &= ~REDIS_NO_AUTO_FREE;
+ if (!(c->flags & REDIS_IN_CALLBACK) && ac->replies.head == NULL)
+ __redisAsyncDisconnect(ac);
+}
+
+static int __redisGetSubscribeCallback(redisAsyncContext *ac, redisReply *reply, redisCallback *dstcb) {
+ redisContext *c = &(ac->c);
+ dict *callbacks;
+ redisCallback *cb;
+ dictEntry *de;
+ int pvariant;
+ char *stype;
+ hisds sname;
+
+ /* 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;
+
+ if (pvariant)
+ callbacks = ac->sub.patterns;
+ else
+ callbacks = ac->sub.channels;
+
+ /* Locate the right callback */
+ assert(reply->element[1]->type == REDIS_REPLY_STRING);
+ sname = hi_sdsnewlen(reply->element[1]->str,reply->element[1]->len);
+ if (sname == NULL)
+ goto oom;
+
+ de = dictFind(callbacks,sname);
+ if (de != NULL) {
+ cb = dictGetEntryVal(de);
+
+ /* If this is an subscribe reply decrease pending counter. */
+ if (strcasecmp(stype+pvariant,"subscribe") == 0) {
+ cb->pending_subs -= 1;
+ }
+
+ memcpy(dstcb,cb,sizeof(*dstcb));
+
+ /* If this is an unsubscribe message, remove it. */
+ if (strcasecmp(stype+pvariant,"unsubscribe") == 0) {
+ if (cb->pending_subs == 0)
+ dictDelete(callbacks,sname);
+
+ /* If this was the last unsubscribe message, revert to
+ * non-subscribe mode. */
+ assert(reply->element[2]->type == REDIS_REPLY_INTEGER);
+
+ /* 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) {
+ 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);
+ } else {
+ /* Shift callback for pending command in subscribed context. */
+ __redisShiftCallback(&ac->sub.replies,dstcb);
+ }
+ return REDIS_OK;
+oom:
+ __redisSetError(&(ac->c), REDIS_ERR_OOM, "Out of memory");
+ return REDIS_ERR;
+}
+
+#define redisIsSpontaneousPushReply(r) \
+ (redisIsPushReply(r) && !redisIsSubscribeReply(r))
+
+static int redisIsSubscribeReply(redisReply *reply) {
+ char *str;
+ size_t len, off;
+
+ /* We will always have at least one string with the subscribe/message type */
+ if (reply->elements < 1 || reply->element[0]->type != REDIS_REPLY_STRING ||
+ reply->element[0]->len < sizeof("message") - 1)
+ {
+ return 0;
+ }
+
+ /* Get the string/len moving past 'p' if needed */
+ off = tolower(reply->element[0]->str[0]) == 'p';
+ str = reply->element[0]->str + off;
+ len = reply->element[0]->len - off;
+
+ return !strncasecmp(str, "subscribe", len) ||
+ !strncasecmp(str, "message", len) ||
+ !strncasecmp(str, "unsubscribe", len);
+}
+
+void redisProcessCallbacks(redisAsyncContext *ac) {
+ redisContext *c = &(ac->c);
+ void *reply = NULL;
+ int status;
+
+ while((status = redisGetReply(c,&reply)) == REDIS_OK) {
+ if (reply == NULL) {
+ /* When the connection is being disconnected and there are
+ * no more replies, this is the cue to really disconnect. */
+ if (c->flags & REDIS_DISCONNECTING && hi_sdslen(c->obuf) == 0
+ && ac->replies.head == NULL) {
+ __redisAsyncDisconnect(ac);
+ return;
+ }
+ /* When the connection is not being disconnected, simply stop
+ * trying to get replies and wait for the next loop tick. */
+ break;
+ }
+
+ /* Keep track of push message support for subscribe handling */
+ if (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
+ * either RESP2 or RESP3 mode. */
+ if (redisIsSpontaneousPushReply(reply)) {
+ __redisRunPushCallback(ac, reply);
+ c->reader->fn->freeObject(reply);
+ continue;
+ }
+
+ /* Even if the context is subscribed, pending regular
+ * callbacks will get a reply before pub/sub messages arrive. */
+ 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
+ * reply that is sent when a new connection exceeds the maximum
+ * number of allowed connections on the server side.
+ *
+ * This is seen as an error instead of a regular reply because the
+ * server closes the connection after sending it.
+ *
+ * To prevent the error from being overwritten by an EOF error the
+ * connection is closed here. See issue #43.
+ *
+ * Another possibility is that the server is loading its dataset.
+ * In this case we also want to close the connection, and have the
+ * user wait until the server is ready to take our request.
+ */
+ if (((redisReply*)reply)->type == REDIS_REPLY_ERROR) {
+ c->err = REDIS_ERR_OTHER;
+ snprintf(c->errstr,sizeof(c->errstr),"%s",((redisReply*)reply)->str);
+ c->reader->fn->freeObject(reply);
+ __redisAsyncDisconnect(ac);
+ return;
+ }
+ /* 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);
+ 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) {
+ __redisAsyncFree(ac);
+ return;
+ }
+ } else {
+ /* No callback for this reply. This can either be a NULL callback,
+ * or there were no callbacks to begin with. Either way, don't
+ * abort with an error, but simply ignore it because the client
+ * doesn't know what the server will spit out over the wire. */
+ c->reader->fn->freeObject(reply);
+ }
+
+ /* If in monitor mode, repush the callback */
+ if (c->flags & REDIS_MONITORING) {
+ __redisPushCallback(&ac->replies,&cb);
+ }
+ }
+
+ /* Disconnect when there was an error reading the reply */
+ if (status != REDIS_OK)
+ __redisAsyncDisconnect(ac);
+}
+
+static void __redisAsyncHandleConnectFailure(redisAsyncContext *ac) {
+ if (ac->onConnect) ac->onConnect(ac, REDIS_ERR);
+ __redisAsyncDisconnect(ac);
+}
+
+/* Internal helper function to detect socket status the first time a read or
+ * write event fires. When connecting was not successful, the connect callback
+ * is called with a REDIS_ERR status and the context is free'd. */
+static int __redisAsyncHandleConnect(redisAsyncContext *ac) {
+ int completed = 0;
+ redisContext *c = &(ac->c);
+
+ if (redisCheckConnectDone(c, &completed) == REDIS_ERR) {
+ /* Error! */
+ if (redisCheckSocketError(c) == REDIS_ERR)
+ __redisAsyncCopyError(ac);
+ __redisAsyncHandleConnectFailure(ac);
+ return REDIS_ERR;
+ } else if (completed == 1) {
+ /* connected! */
+ if (c->connection_type == REDIS_CONN_TCP &&
+ redisSetTcpNoDelay(c) == REDIS_ERR) {
+ __redisAsyncHandleConnectFailure(ac);
+ return REDIS_ERR;
+ }
+
+ if (ac->onConnect) ac->onConnect(ac, REDIS_OK);
+ c->flags |= REDIS_CONNECTED;
+ return REDIS_OK;
+ } else {
+ return REDIS_OK;
+ }
+}
+
+void redisAsyncRead(redisAsyncContext *ac) {
+ redisContext *c = &(ac->c);
+
+ if (redisBufferRead(c) == REDIS_ERR) {
+ __redisAsyncDisconnect(ac);
+ } else {
+ /* Always re-schedule reads */
+ _EL_ADD_READ(ac);
+ redisProcessCallbacks(ac);
+ }
+}
+
+/* This function should be called when the socket is readable.
+ * It processes all replies that can be read and executes their callbacks.
+ */
+void redisAsyncHandleRead(redisAsyncContext *ac) {
+ redisContext *c = &(ac->c);
+
+ if (!(c->flags & REDIS_CONNECTED)) {
+ /* Abort connect was not successful. */
+ if (__redisAsyncHandleConnect(ac) != REDIS_OK)
+ return;
+ /* Try again later when the context is still not connected. */
+ if (!(c->flags & REDIS_CONNECTED))
+ return;
+ }
+
+ c->funcs->async_read(ac);
+}
+
+void redisAsyncWrite(redisAsyncContext *ac) {
+ redisContext *c = &(ac->c);
+ int done = 0;
+
+ if (redisBufferWrite(c,&done) == REDIS_ERR) {
+ __redisAsyncDisconnect(ac);
+ } else {
+ /* Continue writing when not done, stop writing otherwise */
+ if (!done)
+ _EL_ADD_WRITE(ac);
+ else
+ _EL_DEL_WRITE(ac);
+
+ /* Always schedule reads after writes */
+ _EL_ADD_READ(ac);
+ }
+}
+
+void redisAsyncHandleWrite(redisAsyncContext *ac) {
+ redisContext *c = &(ac->c);
+
+ if (!(c->flags & REDIS_CONNECTED)) {
+ /* Abort connect was not successful. */
+ if (__redisAsyncHandleConnect(ac) != REDIS_OK)
+ return;
+ /* Try again later when the context is still not connected. */
+ if (!(c->flags & REDIS_CONNECTED))
+ return;
+ }
+
+ c->funcs->async_write(ac);
+}
+
+void redisAsyncHandleTimeout(redisAsyncContext *ac) {
+ redisContext *c = &(ac->c);
+ redisCallback cb;
+
+ 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) {
+ ac->onConnect(ac, REDIS_ERR);
+ }
+
+ while (__redisShiftCallback(&ac->replies, &cb) == REDIS_OK) {
+ __redisRunCallback(ac, &cb, NULL);
+ }
+
+ /**
+ * TODO: Don't automatically sever the connection,
+ * rather, allow to ignore <x> responses before the queue is clear
+ */
+ __redisAsyncDisconnect(ac);
+}
+
+/* Sets a pointer to the first argument and its length starting at p. Returns
+ * the number of bytes to skip to get to the following argument. */
+static const char *nextArgument(const char *start, const char **str, size_t *len) {
+ const char *p = start;
+ if (p[0] != '$') {
+ p = strchr(p,'$');
+ if (p == NULL) return NULL;
+ }
+
+ *len = (int)strtol(p+1,NULL,10);
+ p = strchr(p,'\r');
+ assert(p);
+ *str = p+2;
+ return p+2+(*len)+2;
+}
+
+/* Helper function for the redisAsyncCommand* family of functions. Writes a
+ * formatted command to the output buffer and registers the provided callback
+ * function with the context. */
+static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *cmd, size_t len) {
+ redisContext *c = &(ac->c);
+ redisCallback cb;
+ struct dict *cbdict;
+ dictEntry *de;
+ redisCallback *existcb;
+ int pvariant, hasnext;
+ const char *cstr, *astr;
+ size_t clen, alen;
+ const char *p;
+ hisds sname;
+ int ret;
+
+ /* Don't accept new commands when the connection is about to be closed. */
+ if (c->flags & (REDIS_DISCONNECTING | REDIS_FREEING)) return REDIS_ERR;
+
+ /* Setup callback */
+ cb.fn = fn;
+ cb.privdata = privdata;
+ cb.pending_subs = 1;
+
+ /* Find out which command will be appended. */
+ p = nextArgument(cmd,&cstr,&clen);
+ assert(p != NULL);
+ hasnext = (p[0] == '$');
+ pvariant = (tolower(cstr[0]) == 'p') ? 1 : 0;
+ cstr += pvariant;
+ clen -= pvariant;
+
+ if (hasnext && strncasecmp(cstr,"subscribe\r\n",11) == 0) {
+ c->flags |= REDIS_SUBSCRIBED;
+
+ /* Add every channel/pattern to the list of subscription callbacks. */
+ while ((p = nextArgument(p,&astr,&alen)) != NULL) {
+ sname = hi_sdsnewlen(astr,alen);
+ if (sname == NULL)
+ goto oom;
+
+ if (pvariant)
+ cbdict = ac->sub.patterns;
+ else
+ cbdict = ac->sub.channels;
+
+ de = dictFind(cbdict,sname);
+
+ if (de != NULL) {
+ existcb = dictGetEntryVal(de);
+ cb.pending_subs = existcb->pending_subs + 1;
+ }
+
+ ret = dictReplace(cbdict,sname,&cb);
+
+ if (ret == 0) hi_sdsfree(sname);
+ }
+ } else if (strncasecmp(cstr,"unsubscribe\r\n",13) == 0) {
+ /* It is only useful to call (P)UNSUBSCRIBE when the context is
+ * subscribed to one or more channels or patterns. */
+ if (!(c->flags & REDIS_SUBSCRIBED)) return REDIS_ERR;
+
+ /* (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;
+ if (__redisPushCallback(&ac->replies,&cb) != REDIS_OK)
+ goto oom;
+ } else {
+ 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);
+
+ /* Always schedule a write when the write buffer is non-empty */
+ _EL_ADD_WRITE(ac);
+
+ return REDIS_OK;
+oom:
+ __redisSetError(&(ac->c), REDIS_ERR_OOM, "Out of memory");
+ __redisAsyncCopyError(ac);
+ return REDIS_ERR;
+}
+
+int redisvAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, va_list ap) {
+ char *cmd;
+ int len;
+ int status;
+ len = redisvFormatCommand(&cmd,format,ap);
+
+ /* We don't want to pass -1 or -2 to future functions as a length. */
+ if (len < 0)
+ return REDIS_ERR;
+
+ status = __redisAsyncCommand(ac,fn,privdata,cmd,len);
+ hi_free(cmd);
+ return status;
+}
+
+int redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, ...) {
+ va_list ap;
+ int status;
+ va_start(ap,format);
+ status = redisvAsyncCommand(ac,fn,privdata,format,ap);
+ va_end(ap);
+ return status;
+}
+
+int redisAsyncCommandArgv(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen) {
+ hisds 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);
+ return status;
+}
+
+int redisAsyncFormattedCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *cmd, size_t len) {
+ int status = __redisAsyncCommand(ac,fn,privdata,cmd,len);
+ return status;
+}
+
+redisAsyncPushFn *redisAsyncSetPushCallback(redisAsyncContext *ac, redisAsyncPushFn *fn) {
+ redisAsyncPushFn *old = ac->push_cb;
+ ac->push_cb = fn;
+ return old;
+}
+
+int redisAsyncSetTimeout(redisAsyncContext *ac, struct timeval tv) {
+ if (!ac->c.command_timeout) {
+ ac->c.command_timeout = hi_calloc(1, sizeof(tv));
+ if (ac->c.command_timeout == NULL) {
+ __redisSetError(&ac->c, REDIS_ERR_OOM, "Out of memory");
+ __redisAsyncCopyError(ac);
+ return REDIS_ERR;
+ }
+ }
+
+ if (tv.tv_sec != ac->c.command_timeout->tv_sec ||
+ tv.tv_usec != ac->c.command_timeout->tv_usec)
+ {
+ *ac->c.command_timeout = tv;
+ }
+
+ return REDIS_OK;
+}
diff --git a/deps/hiredis/async.h b/deps/hiredis/async.h
new file mode 100644
index 0000000..4c65203
--- /dev/null
+++ b/deps/hiredis/async.h
@@ -0,0 +1,147 @@
+/*
+ * Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com>
+ * Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com>
+ *
+ * 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.
+ */
+
+#ifndef __HIREDIS_ASYNC_H
+#define __HIREDIS_ASYNC_H
+#include "hiredis.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct redisAsyncContext; /* need forward declaration of redisAsyncContext */
+struct dict; /* dictionary header is included in async.c */
+
+/* Reply callback prototype and container */
+typedef void (redisCallbackFn)(struct redisAsyncContext*, void*, void*);
+typedef struct redisCallback {
+ struct redisCallback *next; /* simple singly linked list */
+ redisCallbackFn *fn;
+ int pending_subs;
+ void *privdata;
+} redisCallback;
+
+/* List of callbacks for either regular replies or pub/sub */
+typedef struct redisCallbackList {
+ redisCallback *head, *tail;
+} redisCallbackList;
+
+/* Connection callback prototypes */
+typedef void (redisDisconnectCallback)(const struct redisAsyncContext*, int status);
+typedef void (redisConnectCallback)(const struct redisAsyncContext*, int status);
+typedef void(redisTimerCallback)(void *timer, void *privdata);
+
+/* Context for an async connection to Redis */
+typedef struct redisAsyncContext {
+ /* Hold the regular context, so it can be realloc'ed. */
+ redisContext c;
+
+ /* Setup error flags so they can be used directly. */
+ int err;
+ char *errstr;
+
+ /* Not used by hiredis */
+ void *data;
+ void (*dataCleanup)(void *privdata);
+
+ /* Event library data and hooks */
+ struct {
+ void *data;
+
+ /* Hooks that are called when the library expects to start
+ * reading/writing. These functions should be idempotent. */
+ void (*addRead)(void *privdata);
+ void (*delRead)(void *privdata);
+ void (*addWrite)(void *privdata);
+ void (*delWrite)(void *privdata);
+ void (*cleanup)(void *privdata);
+ void (*scheduleTimer)(void *privdata, struct timeval tv);
+ } ev;
+
+ /* Called when either the connection is terminated due to an error or per
+ * user request. The status is set accordingly (REDIS_OK, REDIS_ERR). */
+ redisDisconnectCallback *onDisconnect;
+
+ /* Called when the first write event was received. */
+ redisConnectCallback *onConnect;
+
+ /* Regular command callbacks */
+ redisCallbackList replies;
+
+ /* Address used for connect() */
+ struct sockaddr *saddr;
+ size_t addrlen;
+
+ /* Subscription callbacks */
+ struct {
+ redisCallbackList replies;
+ struct dict *channels;
+ struct dict *patterns;
+ } sub;
+
+ /* Any configured RESP3 PUSH handler */
+ redisAsyncPushFn *push_cb;
+} redisAsyncContext;
+
+/* Functions that proxy to hiredis */
+redisAsyncContext *redisAsyncConnectWithOptions(const redisOptions *options);
+redisAsyncContext *redisAsyncConnect(const char *ip, int port);
+redisAsyncContext *redisAsyncConnectBind(const char *ip, int port, const char *source_addr);
+redisAsyncContext *redisAsyncConnectBindWithReuse(const char *ip, int port,
+ const char *source_addr);
+redisAsyncContext *redisAsyncConnectUnix(const char *path);
+int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn);
+int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn);
+
+redisAsyncPushFn *redisAsyncSetPushCallback(redisAsyncContext *ac, redisAsyncPushFn *fn);
+int redisAsyncSetTimeout(redisAsyncContext *ac, struct timeval tv);
+void redisAsyncDisconnect(redisAsyncContext *ac);
+void redisAsyncFree(redisAsyncContext *ac);
+
+/* Handle read/write events */
+void redisAsyncHandleRead(redisAsyncContext *ac);
+void redisAsyncHandleWrite(redisAsyncContext *ac);
+void redisAsyncHandleTimeout(redisAsyncContext *ac);
+void redisAsyncRead(redisAsyncContext *ac);
+void redisAsyncWrite(redisAsyncContext *ac);
+
+/* Command functions for an async context. Write the command to the
+ * output buffer and register the provided callback. */
+int redisvAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, va_list ap);
+int redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, ...);
+int redisAsyncCommandArgv(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen);
+int redisAsyncFormattedCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *cmd, size_t len);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/deps/hiredis/async_private.h b/deps/hiredis/async_private.h
new file mode 100644
index 0000000..ea0558d
--- /dev/null
+++ b/deps/hiredis/async_private.h
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com>
+ * Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com>
+ *
+ * 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.
+ */
+
+#ifndef __HIREDIS_ASYNC_PRIVATE_H
+#define __HIREDIS_ASYNC_PRIVATE_H
+
+#define _EL_ADD_READ(ctx) \
+ do { \
+ refreshTimeout(ctx); \
+ if ((ctx)->ev.addRead) (ctx)->ev.addRead((ctx)->ev.data); \
+ } while (0)
+#define _EL_DEL_READ(ctx) do { \
+ if ((ctx)->ev.delRead) (ctx)->ev.delRead((ctx)->ev.data); \
+ } while(0)
+#define _EL_ADD_WRITE(ctx) \
+ do { \
+ refreshTimeout(ctx); \
+ if ((ctx)->ev.addWrite) (ctx)->ev.addWrite((ctx)->ev.data); \
+ } while (0)
+#define _EL_DEL_WRITE(ctx) do { \
+ if ((ctx)->ev.delWrite) (ctx)->ev.delWrite((ctx)->ev.data); \
+ } while(0)
+#define _EL_CLEANUP(ctx) do { \
+ if ((ctx)->ev.cleanup) (ctx)->ev.cleanup((ctx)->ev.data); \
+ ctx->ev.cleanup = NULL; \
+ } while(0)
+
+static inline void refreshTimeout(redisAsyncContext *ctx) {
+ #define REDIS_TIMER_ISSET(tvp) \
+ (tvp && ((tvp)->tv_sec || (tvp)->tv_usec))
+
+ #define REDIS_EL_TIMER(ac, tvp) \
+ if ((ac)->ev.scheduleTimer && REDIS_TIMER_ISSET(tvp)) { \
+ (ac)->ev.scheduleTimer((ac)->ev.data, *(tvp)); \
+ }
+
+ if (ctx->c.flags & REDIS_CONNECTED) {
+ REDIS_EL_TIMER(ctx, ctx->c.command_timeout);
+ } else {
+ REDIS_EL_TIMER(ctx, ctx->c.connect_timeout);
+ }
+}
+
+void __redisAsyncDisconnect(redisAsyncContext *ac);
+void redisProcessCallbacks(redisAsyncContext *ac);
+
+#endif /* __HIREDIS_ASYNC_PRIVATE_H */
diff --git a/deps/hiredis/dict.c b/deps/hiredis/dict.c
new file mode 100644
index 0000000..ad57181
--- /dev/null
+++ b/deps/hiredis/dict.c
@@ -0,0 +1,343 @@
+/* Hash table implementation.
+ *
+ * This file implements in memory hash tables with insert/del/replace/find/
+ * get-random-element operations. Hash tables will auto resize if needed
+ * tables of power of two in size are used, collisions are handled by
+ * chaining. See the source code for more information... :)
+ *
+ * Copyright (c) 2006-2010, Salvatore Sanfilippo <antirez at gmail dot com>
+ * 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 "fmacros.h"
+#include "alloc.h"
+#include <stdlib.h>
+#include <assert.h>
+#include <limits.h>
+#include "dict.h"
+
+/* -------------------------- private prototypes ---------------------------- */
+
+static int _dictExpandIfNeeded(dict *ht);
+static unsigned long _dictNextPower(unsigned long size);
+static int _dictKeyIndex(dict *ht, const void *key);
+static int _dictInit(dict *ht, dictType *type, void *privDataPtr);
+
+/* -------------------------- hash functions -------------------------------- */
+
+/* Generic hash function (a popular one from Bernstein).
+ * I tested a few and this was the best. */
+static unsigned int dictGenHashFunction(const unsigned char *buf, int len) {
+ unsigned int hash = 5381;
+
+ while (len--)
+ hash = ((hash << 5) + hash) + (*buf++); /* hash * 33 + c */
+ return hash;
+}
+
+/* ----------------------------- API implementation ------------------------- */
+
+/* Reset an hashtable already initialized with ht_init().
+ * NOTE: This function should only called by ht_destroy(). */
+static void _dictReset(dict *ht) {
+ ht->table = NULL;
+ ht->size = 0;
+ ht->sizemask = 0;
+ ht->used = 0;
+}
+
+/* Create a new hash table */
+static dict *dictCreate(dictType *type, void *privDataPtr) {
+ dict *ht = hi_malloc(sizeof(*ht));
+ if (ht == NULL)
+ return NULL;
+
+ _dictInit(ht,type,privDataPtr);
+ return ht;
+}
+
+/* Initialize the hash table */
+static int _dictInit(dict *ht, dictType *type, void *privDataPtr) {
+ _dictReset(ht);
+ ht->type = type;
+ ht->privdata = privDataPtr;
+ return DICT_OK;
+}
+
+/* Expand or create the hashtable */
+static int dictExpand(dict *ht, unsigned long size) {
+ dict n; /* the new hashtable */
+ unsigned long realsize = _dictNextPower(size), i;
+
+ /* the size is invalid if it is smaller than the number of
+ * elements already inside the hashtable */
+ if (ht->used > size)
+ return DICT_ERR;
+
+ _dictInit(&n, ht->type, ht->privdata);
+ n.size = realsize;
+ n.sizemask = realsize-1;
+ n.table = hi_calloc(realsize,sizeof(dictEntry*));
+ if (n.table == NULL)
+ return DICT_ERR;
+
+ /* Copy all the elements from the old to the new table:
+ * note that if the old hash table is empty ht->size is zero,
+ * so dictExpand just creates an hash table. */
+ n.used = ht->used;
+ for (i = 0; i < ht->size && ht->used > 0; i++) {
+ dictEntry *he, *nextHe;
+
+ if (ht->table[i] == NULL) continue;
+
+ /* For each hash entry on this slot... */
+ he = ht->table[i];
+ while(he) {
+ unsigned int h;
+
+ nextHe = he->next;
+ /* Get the new element index */
+ h = dictHashKey(ht, he->key) & n.sizemask;
+ he->next = n.table[h];
+ n.table[h] = he;
+ ht->used--;
+ /* Pass to the next element */
+ he = nextHe;
+ }
+ }
+ assert(ht->used == 0);
+ hi_free(ht->table);
+
+ /* Remap the new hashtable in the old */
+ *ht = n;
+ return DICT_OK;
+}
+
+/* Add an element to the target hash table */
+static int dictAdd(dict *ht, void *key, void *val) {
+ int index;
+ dictEntry *entry;
+
+ /* Get the index of the new element, or -1 if
+ * the element already exists. */
+ if ((index = _dictKeyIndex(ht, key)) == -1)
+ return DICT_ERR;
+
+ /* Allocates the memory and stores key */
+ entry = hi_malloc(sizeof(*entry));
+ if (entry == NULL)
+ return DICT_ERR;
+
+ entry->next = ht->table[index];
+ ht->table[index] = entry;
+
+ /* Set the hash entry fields. */
+ dictSetHashKey(ht, entry, key);
+ dictSetHashVal(ht, entry, val);
+ ht->used++;
+ return DICT_OK;
+}
+
+/* Add an element, discarding the old if the key already exists.
+ * Return 1 if the key was added from scratch, 0 if there was already an
+ * element with such key and dictReplace() just performed a value update
+ * operation. */
+static int dictReplace(dict *ht, void *key, void *val) {
+ dictEntry *entry, auxentry;
+
+ /* Try to add the element. If the key
+ * does not exists dictAdd will succeed. */
+ if (dictAdd(ht, key, val) == DICT_OK)
+ return 1;
+ /* It already exists, get the entry */
+ entry = dictFind(ht, key);
+ if (entry == NULL)
+ return 0;
+
+ /* Free the old value and set the new one */
+ /* Set the new value and free the old one. Note that it is important
+ * to do that in this order, as the value may just be exactly the same
+ * as the previous one. In this context, think to reference counting,
+ * you want to increment (set), and then decrement (free), and not the
+ * reverse. */
+ auxentry = *entry;
+ dictSetHashVal(ht, entry, val);
+ dictFreeEntryVal(ht, &auxentry);
+ return 0;
+}
+
+/* Search and remove an element */
+static int dictDelete(dict *ht, const void *key) {
+ unsigned int h;
+ dictEntry *de, *prevde;
+
+ if (ht->size == 0)
+ return DICT_ERR;
+ h = dictHashKey(ht, key) & ht->sizemask;
+ de = ht->table[h];
+
+ prevde = NULL;
+ while(de) {
+ if (dictCompareHashKeys(ht,key,de->key)) {
+ /* Unlink the element from the list */
+ if (prevde)
+ prevde->next = de->next;
+ else
+ ht->table[h] = de->next;
+
+ dictFreeEntryKey(ht,de);
+ dictFreeEntryVal(ht,de);
+ hi_free(de);
+ ht->used--;
+ return DICT_OK;
+ }
+ prevde = de;
+ de = de->next;
+ }
+ return DICT_ERR; /* not found */
+}
+
+/* Destroy an entire hash table */
+static int _dictClear(dict *ht) {
+ unsigned long i;
+
+ /* Free all the elements */
+ for (i = 0; i < ht->size && ht->used > 0; i++) {
+ dictEntry *he, *nextHe;
+
+ if ((he = ht->table[i]) == NULL) continue;
+ while(he) {
+ nextHe = he->next;
+ dictFreeEntryKey(ht, he);
+ dictFreeEntryVal(ht, he);
+ hi_free(he);
+ ht->used--;
+ he = nextHe;
+ }
+ }
+ /* Free the table and the allocated cache structure */
+ hi_free(ht->table);
+ /* Re-initialize the table */
+ _dictReset(ht);
+ return DICT_OK; /* never fails */
+}
+
+/* Clear & Release the hash table */
+static void dictRelease(dict *ht) {
+ _dictClear(ht);
+ hi_free(ht);
+}
+
+static dictEntry *dictFind(dict *ht, const void *key) {
+ dictEntry *he;
+ unsigned int h;
+
+ if (ht->size == 0) return NULL;
+ h = dictHashKey(ht, key) & ht->sizemask;
+ he = ht->table[h];
+ while(he) {
+ if (dictCompareHashKeys(ht, key, he->key))
+ return he;
+ he = he->next;
+ }
+ return NULL;
+}
+
+static void dictInitIterator(dictIterator *iter, dict *ht) {
+ iter->ht = ht;
+ iter->index = -1;
+ iter->entry = NULL;
+ iter->nextEntry = NULL;
+}
+
+static dictEntry *dictNext(dictIterator *iter) {
+ while (1) {
+ if (iter->entry == NULL) {
+ iter->index++;
+ if (iter->index >=
+ (signed)iter->ht->size) break;
+ iter->entry = iter->ht->table[iter->index];
+ } else {
+ iter->entry = iter->nextEntry;
+ }
+ if (iter->entry) {
+ /* We need to save the 'next' here, the iterator user
+ * may delete the entry we are returning. */
+ iter->nextEntry = iter->entry->next;
+ return iter->entry;
+ }
+ }
+ return NULL;
+}
+
+/* ------------------------- private functions ------------------------------ */
+
+/* Expand the hash table if needed */
+static int _dictExpandIfNeeded(dict *ht) {
+ /* If the hash table is empty expand it to the initial size,
+ * if the table is "full" double its size. */
+ if (ht->size == 0)
+ return dictExpand(ht, DICT_HT_INITIAL_SIZE);
+ if (ht->used == ht->size)
+ return dictExpand(ht, ht->size*2);
+ return DICT_OK;
+}
+
+/* Our hash table capability is a power of two */
+static unsigned long _dictNextPower(unsigned long size) {
+ unsigned long i = DICT_HT_INITIAL_SIZE;
+
+ if (size >= LONG_MAX) return LONG_MAX;
+ while(1) {
+ if (i >= size)
+ return i;
+ i *= 2;
+ }
+}
+
+/* Returns the index of a free slot that can be populated with
+ * an hash entry for the given 'key'.
+ * If the key already exists, -1 is returned. */
+static int _dictKeyIndex(dict *ht, const void *key) {
+ unsigned int h;
+ dictEntry *he;
+
+ /* Expand the hashtable if needed */
+ if (_dictExpandIfNeeded(ht) == DICT_ERR)
+ return -1;
+ /* Compute the key hash value */
+ h = dictHashKey(ht, key) & ht->sizemask;
+ /* Search if this slot does not already contain the given key */
+ he = ht->table[h];
+ while(he) {
+ if (dictCompareHashKeys(ht, key, he->key))
+ return -1;
+ he = he->next;
+ }
+ return h;
+}
+
diff --git a/deps/hiredis/dict.h b/deps/hiredis/dict.h
new file mode 100644
index 0000000..6ad0acd
--- /dev/null
+++ b/deps/hiredis/dict.h
@@ -0,0 +1,125 @@
+/* Hash table implementation.
+ *
+ * This file implements in memory hash tables with insert/del/replace/find/
+ * get-random-element operations. Hash tables will auto resize if needed
+ * tables of power of two in size are used, collisions are handled by
+ * chaining. See the source code for more information... :)
+ *
+ * Copyright (c) 2006-2010, Salvatore Sanfilippo <antirez at gmail dot com>
+ * 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.
+ */
+
+#ifndef __DICT_H
+#define __DICT_H
+
+#define DICT_OK 0
+#define DICT_ERR 1
+
+/* Unused arguments generate annoying warnings... */
+#define DICT_NOTUSED(V) ((void) V)
+
+typedef struct dictEntry {
+ void *key;
+ void *val;
+ struct dictEntry *next;
+} dictEntry;
+
+typedef struct dictType {
+ unsigned int (*hashFunction)(const void *key);
+ void *(*keyDup)(void *privdata, const void *key);
+ void *(*valDup)(void *privdata, const void *obj);
+ int (*keyCompare)(void *privdata, const void *key1, const void *key2);
+ void (*keyDestructor)(void *privdata, void *key);
+ void (*valDestructor)(void *privdata, void *obj);
+} dictType;
+
+typedef struct dict {
+ dictEntry **table;
+ dictType *type;
+ unsigned long size;
+ unsigned long sizemask;
+ unsigned long used;
+ void *privdata;
+} dict;
+
+typedef struct dictIterator {
+ dict *ht;
+ int index;
+ dictEntry *entry, *nextEntry;
+} dictIterator;
+
+/* This is the initial size of every hash table */
+#define DICT_HT_INITIAL_SIZE 4
+
+/* ------------------------------- Macros ------------------------------------*/
+#define dictFreeEntryVal(ht, entry) \
+ if ((ht)->type->valDestructor) \
+ (ht)->type->valDestructor((ht)->privdata, (entry)->val)
+
+#define dictSetHashVal(ht, entry, _val_) do { \
+ if ((ht)->type->valDup) \
+ entry->val = (ht)->type->valDup((ht)->privdata, _val_); \
+ else \
+ entry->val = (_val_); \
+} while(0)
+
+#define dictFreeEntryKey(ht, entry) \
+ if ((ht)->type->keyDestructor) \
+ (ht)->type->keyDestructor((ht)->privdata, (entry)->key)
+
+#define dictSetHashKey(ht, entry, _key_) do { \
+ if ((ht)->type->keyDup) \
+ entry->key = (ht)->type->keyDup((ht)->privdata, _key_); \
+ else \
+ entry->key = (_key_); \
+} while(0)
+
+#define dictCompareHashKeys(ht, key1, key2) \
+ (((ht)->type->keyCompare) ? \
+ (ht)->type->keyCompare((ht)->privdata, key1, key2) : \
+ (key1) == (key2))
+
+#define dictHashKey(ht, key) (ht)->type->hashFunction(key)
+
+#define dictGetEntryKey(he) ((he)->key)
+#define dictGetEntryVal(he) ((he)->val)
+#define dictSlots(ht) ((ht)->size)
+#define dictSize(ht) ((ht)->used)
+
+/* API */
+static unsigned int dictGenHashFunction(const unsigned char *buf, int len);
+static dict *dictCreate(dictType *type, void *privDataPtr);
+static int dictExpand(dict *ht, unsigned long size);
+static int dictAdd(dict *ht, void *key, void *val);
+static int dictReplace(dict *ht, void *key, void *val);
+static int dictDelete(dict *ht, const void *key);
+static void dictRelease(dict *ht);
+static dictEntry * dictFind(dict *ht, const void *key);
+static void dictInitIterator(dictIterator *iter, dict *ht);
+static dictEntry *dictNext(dictIterator *iter);
+
+#endif /* __DICT_H */
diff --git a/deps/hiredis/examples/CMakeLists.txt b/deps/hiredis/examples/CMakeLists.txt
new file mode 100644
index 0000000..49cd8d4
--- /dev/null
+++ b/deps/hiredis/examples/CMakeLists.txt
@@ -0,0 +1,49 @@
+INCLUDE(FindPkgConfig)
+# Check for GLib
+
+PKG_CHECK_MODULES(GLIB2 glib-2.0)
+if (GLIB2_FOUND)
+ INCLUDE_DIRECTORIES(${GLIB2_INCLUDE_DIRS})
+ LINK_DIRECTORIES(${GLIB2_LIBRARY_DIRS})
+ ADD_EXECUTABLE(example-glib example-glib.c)
+ TARGET_LINK_LIBRARIES(example-glib hiredis ${GLIB2_LIBRARIES})
+ENDIF(GLIB2_FOUND)
+
+FIND_PATH(LIBEV ev.h
+ HINTS /usr/local /usr/opt/local
+ ENV LIBEV_INCLUDE_DIR)
+
+if (LIBEV)
+ # Just compile and link with libev
+ ADD_EXECUTABLE(example-libev example-libev.c)
+ TARGET_LINK_LIBRARIES(example-libev hiredis ev)
+ENDIF()
+
+FIND_PATH(LIBEVENT event.h)
+if (LIBEVENT)
+ ADD_EXECUTABLE(example-libevent example-libevent.c)
+ TARGET_LINK_LIBRARIES(example-libevent hiredis event)
+ENDIF()
+
+FIND_PATH(LIBUV uv.h)
+IF (LIBUV)
+ ADD_EXECUTABLE(example-libuv example-libuv.c)
+ TARGET_LINK_LIBRARIES(example-libuv hiredis uv)
+ENDIF()
+
+IF (APPLE)
+ FIND_LIBRARY(CF CoreFoundation)
+ ADD_EXECUTABLE(example-macosx example-macosx.c)
+ TARGET_LINK_LIBRARIES(example-macosx hiredis ${CF})
+ENDIF()
+
+IF (ENABLE_SSL)
+ ADD_EXECUTABLE(example-ssl example-ssl.c)
+ TARGET_LINK_LIBRARIES(example-ssl hiredis hiredis_ssl)
+ENDIF()
+
+ADD_EXECUTABLE(example example.c)
+TARGET_LINK_LIBRARIES(example hiredis)
+
+ADD_EXECUTABLE(example-push example-push.c)
+TARGET_LINK_LIBRARIES(example-push hiredis)
diff --git a/deps/hiredis/examples/example-ae.c b/deps/hiredis/examples/example-ae.c
new file mode 100644
index 0000000..8efa730
--- /dev/null
+++ b/deps/hiredis/examples/example-ae.c
@@ -0,0 +1,62 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+
+#include <hiredis.h>
+#include <async.h>
+#include <adapters/ae.h>
+
+/* Put event loop in the global scope, so it can be explicitly stopped */
+static aeEventLoop *loop;
+
+void getCallback(redisAsyncContext *c, void *r, void *privdata) {
+ redisReply *reply = r;
+ if (reply == NULL) return;
+ printf("argv[%s]: %s\n", (char*)privdata, reply->str);
+
+ /* Disconnect after receiving the reply to GET */
+ redisAsyncDisconnect(c);
+}
+
+void connectCallback(const redisAsyncContext *c, int status) {
+ if (status != REDIS_OK) {
+ printf("Error: %s\n", c->errstr);
+ aeStop(loop);
+ return;
+ }
+
+ printf("Connected...\n");
+}
+
+void disconnectCallback(const redisAsyncContext *c, int status) {
+ if (status != REDIS_OK) {
+ printf("Error: %s\n", c->errstr);
+ aeStop(loop);
+ return;
+ }
+
+ printf("Disconnected...\n");
+ aeStop(loop);
+}
+
+int main (int argc, char **argv) {
+ signal(SIGPIPE, SIG_IGN);
+
+ redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379);
+ if (c->err) {
+ /* Let *c leak for now... */
+ printf("Error: %s\n", c->errstr);
+ return 1;
+ }
+
+ loop = aeCreateEventLoop(64);
+ redisAeAttach(loop, c);
+ redisAsyncSetConnectCallback(c,connectCallback);
+ redisAsyncSetDisconnectCallback(c,disconnectCallback);
+ redisAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1]));
+ redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key");
+ aeMain(loop);
+ return 0;
+}
+
diff --git a/deps/hiredis/examples/example-glib.c b/deps/hiredis/examples/example-glib.c
new file mode 100644
index 0000000..d6e10f8
--- /dev/null
+++ b/deps/hiredis/examples/example-glib.c
@@ -0,0 +1,73 @@
+#include <stdlib.h>
+
+#include <hiredis.h>
+#include <async.h>
+#include <adapters/glib.h>
+
+static GMainLoop *mainloop;
+
+static void
+connect_cb (const redisAsyncContext *ac G_GNUC_UNUSED,
+ int status)
+{
+ if (status != REDIS_OK) {
+ g_printerr("Failed to connect: %s\n", ac->errstr);
+ g_main_loop_quit(mainloop);
+ } else {
+ g_printerr("Connected...\n");
+ }
+}
+
+static void
+disconnect_cb (const redisAsyncContext *ac G_GNUC_UNUSED,
+ int status)
+{
+ if (status != REDIS_OK) {
+ g_error("Failed to disconnect: %s", ac->errstr);
+ } else {
+ g_printerr("Disconnected...\n");
+ g_main_loop_quit(mainloop);
+ }
+}
+
+static void
+command_cb(redisAsyncContext *ac,
+ gpointer r,
+ gpointer user_data G_GNUC_UNUSED)
+{
+ redisReply *reply = r;
+
+ if (reply) {
+ g_print("REPLY: %s\n", reply->str);
+ }
+
+ redisAsyncDisconnect(ac);
+}
+
+gint
+main (gint argc G_GNUC_UNUSED,
+ gchar *argv[] G_GNUC_UNUSED)
+{
+ redisAsyncContext *ac;
+ GMainContext *context = NULL;
+ GSource *source;
+
+ ac = redisAsyncConnect("127.0.0.1", 6379);
+ if (ac->err) {
+ g_printerr("%s\n", ac->errstr);
+ exit(EXIT_FAILURE);
+ }
+
+ source = redis_source_new(ac);
+ mainloop = g_main_loop_new(context, FALSE);
+ g_source_attach(source, context);
+
+ redisAsyncSetConnectCallback(ac, connect_cb);
+ redisAsyncSetDisconnectCallback(ac, disconnect_cb);
+ redisAsyncCommand(ac, command_cb, NULL, "SET key 1234");
+ redisAsyncCommand(ac, command_cb, NULL, "GET key");
+
+ g_main_loop_run(mainloop);
+
+ return EXIT_SUCCESS;
+}
diff --git a/deps/hiredis/examples/example-ivykis.c b/deps/hiredis/examples/example-ivykis.c
new file mode 100644
index 0000000..f57dc38
--- /dev/null
+++ b/deps/hiredis/examples/example-ivykis.c
@@ -0,0 +1,60 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+
+#include <hiredis.h>
+#include <async.h>
+#include <adapters/ivykis.h>
+
+void getCallback(redisAsyncContext *c, void *r, void *privdata) {
+ redisReply *reply = r;
+ if (reply == NULL) return;
+ printf("argv[%s]: %s\n", (char*)privdata, reply->str);
+
+ /* Disconnect after receiving the reply to GET */
+ redisAsyncDisconnect(c);
+}
+
+void connectCallback(const redisAsyncContext *c, int status) {
+ if (status != REDIS_OK) {
+ printf("Error: %s\n", c->errstr);
+ return;
+ }
+ printf("Connected...\n");
+}
+
+void disconnectCallback(const redisAsyncContext *c, int status) {
+ if (status != REDIS_OK) {
+ printf("Error: %s\n", c->errstr);
+ return;
+ }
+ printf("Disconnected...\n");
+}
+
+int main (int argc, char **argv) {
+#ifndef _WIN32
+ signal(SIGPIPE, SIG_IGN);
+#endif
+
+ iv_init();
+
+ redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379);
+ if (c->err) {
+ /* Let *c leak for now... */
+ printf("Error: %s\n", c->errstr);
+ return 1;
+ }
+
+ redisIvykisAttach(c);
+ redisAsyncSetConnectCallback(c,connectCallback);
+ redisAsyncSetDisconnectCallback(c,disconnectCallback);
+ redisAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1]));
+ redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key");
+
+ iv_main();
+
+ iv_deinit();
+
+ return 0;
+}
diff --git a/deps/hiredis/examples/example-libev.c b/deps/hiredis/examples/example-libev.c
new file mode 100644
index 0000000..ec47430
--- /dev/null
+++ b/deps/hiredis/examples/example-libev.c
@@ -0,0 +1,54 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+
+#include <hiredis.h>
+#include <async.h>
+#include <adapters/libev.h>
+
+void getCallback(redisAsyncContext *c, void *r, void *privdata) {
+ redisReply *reply = r;
+ if (reply == NULL) return;
+ printf("argv[%s]: %s\n", (char*)privdata, reply->str);
+
+ /* Disconnect after receiving the reply to GET */
+ redisAsyncDisconnect(c);
+}
+
+void connectCallback(const redisAsyncContext *c, int status) {
+ if (status != REDIS_OK) {
+ printf("Error: %s\n", c->errstr);
+ return;
+ }
+ printf("Connected...\n");
+}
+
+void disconnectCallback(const redisAsyncContext *c, int status) {
+ if (status != REDIS_OK) {
+ printf("Error: %s\n", c->errstr);
+ return;
+ }
+ printf("Disconnected...\n");
+}
+
+int main (int argc, char **argv) {
+#ifndef _WIN32
+ signal(SIGPIPE, SIG_IGN);
+#endif
+
+ redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379);
+ if (c->err) {
+ /* Let *c leak for now... */
+ printf("Error: %s\n", c->errstr);
+ return 1;
+ }
+
+ redisLibevAttach(EV_DEFAULT_ c);
+ redisAsyncSetConnectCallback(c,connectCallback);
+ redisAsyncSetDisconnectCallback(c,disconnectCallback);
+ redisAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1]));
+ redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key");
+ ev_loop(EV_DEFAULT_ 0);
+ return 0;
+}
diff --git a/deps/hiredis/examples/example-libevent-ssl.c b/deps/hiredis/examples/example-libevent-ssl.c
new file mode 100644
index 0000000..7d99af1
--- /dev/null
+++ b/deps/hiredis/examples/example-libevent-ssl.c
@@ -0,0 +1,90 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+
+#include <hiredis.h>
+#include <hiredis_ssl.h>
+#include <async.h>
+#include <adapters/libevent.h>
+
+void getCallback(redisAsyncContext *c, void *r, void *privdata) {
+ redisReply *reply = r;
+ if (reply == NULL) return;
+ printf("argv[%s]: %s\n", (char*)privdata, reply->str);
+
+ /* Disconnect after receiving the reply to GET */
+ redisAsyncDisconnect(c);
+}
+
+void connectCallback(const redisAsyncContext *c, int status) {
+ if (status != REDIS_OK) {
+ printf("Error: %s\n", c->errstr);
+ return;
+ }
+ printf("Connected...\n");
+}
+
+void disconnectCallback(const redisAsyncContext *c, int status) {
+ if (status != REDIS_OK) {
+ printf("Error: %s\n", c->errstr);
+ return;
+ }
+ printf("Disconnected...\n");
+}
+
+int main (int argc, char **argv) {
+#ifndef _WIN32
+ signal(SIGPIPE, SIG_IGN);
+#endif
+
+ struct event_base *base = event_base_new();
+ if (argc < 5) {
+ fprintf(stderr,
+ "Usage: %s <key> <host> <port> <cert> <certKey> [ca]\n", argv[0]);
+ exit(1);
+ }
+
+ const char *value = argv[1];
+ size_t nvalue = strlen(value);
+
+ const char *hostname = argv[2];
+ int port = atoi(argv[3]);
+
+ const char *cert = argv[4];
+ const char *certKey = argv[5];
+ const char *caCert = argc > 5 ? argv[6] : NULL;
+
+ redisSSLContext *ssl;
+ redisSSLContextError ssl_error;
+
+ redisInitOpenSSL();
+
+ ssl = redisCreateSSLContext(caCert, NULL,
+ cert, certKey, NULL, &ssl_error);
+ if (!ssl) {
+ printf("Error: %s\n", redisSSLContextGetError(ssl_error));
+ return 1;
+ }
+
+ redisAsyncContext *c = redisAsyncConnect(hostname, port);
+ if (c->err) {
+ /* Let *c leak for now... */
+ printf("Error: %s\n", c->errstr);
+ return 1;
+ }
+ if (redisInitiateSSLWithContext(&c->c, ssl) != REDIS_OK) {
+ printf("SSL Error!\n");
+ exit(1);
+ }
+
+ redisLibeventAttach(c,base);
+ redisAsyncSetConnectCallback(c,connectCallback);
+ redisAsyncSetDisconnectCallback(c,disconnectCallback);
+ redisAsyncCommand(c, NULL, NULL, "SET key %b", value, nvalue);
+ redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key");
+ event_base_dispatch(base);
+
+ redisFreeSSLContext(ssl);
+ return 0;
+}
diff --git a/deps/hiredis/examples/example-libevent.c b/deps/hiredis/examples/example-libevent.c
new file mode 100644
index 0000000..49bddd0
--- /dev/null
+++ b/deps/hiredis/examples/example-libevent.c
@@ -0,0 +1,67 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+
+#include <hiredis.h>
+#include <async.h>
+#include <adapters/libevent.h>
+
+void getCallback(redisAsyncContext *c, void *r, void *privdata) {
+ redisReply *reply = r;
+ if (reply == NULL) {
+ if (c->errstr) {
+ printf("errstr: %s\n", c->errstr);
+ }
+ return;
+ }
+ printf("argv[%s]: %s\n", (char*)privdata, reply->str);
+
+ /* Disconnect after receiving the reply to GET */
+ redisAsyncDisconnect(c);
+}
+
+void connectCallback(const redisAsyncContext *c, int status) {
+ if (status != REDIS_OK) {
+ printf("Error: %s\n", c->errstr);
+ return;
+ }
+ printf("Connected...\n");
+}
+
+void disconnectCallback(const redisAsyncContext *c, int status) {
+ if (status != REDIS_OK) {
+ printf("Error: %s\n", c->errstr);
+ return;
+ }
+ printf("Disconnected...\n");
+}
+
+int main (int argc, char **argv) {
+#ifndef _WIN32
+ signal(SIGPIPE, SIG_IGN);
+#endif
+
+ struct event_base *base = event_base_new();
+ redisOptions options = {0};
+ REDIS_OPTIONS_SET_TCP(&options, "127.0.0.1", 6379);
+ struct timeval tv = {0};
+ tv.tv_sec = 1;
+ options.connect_timeout = &tv;
+
+
+ redisAsyncContext *c = redisAsyncConnectWithOptions(&options);
+ if (c->err) {
+ /* Let *c leak for now... */
+ printf("Error: %s\n", c->errstr);
+ return 1;
+ }
+
+ redisLibeventAttach(c,base);
+ redisAsyncSetConnectCallback(c,connectCallback);
+ redisAsyncSetDisconnectCallback(c,disconnectCallback);
+ redisAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1]));
+ redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key");
+ event_base_dispatch(base);
+ return 0;
+}
diff --git a/deps/hiredis/examples/example-libuv.c b/deps/hiredis/examples/example-libuv.c
new file mode 100644
index 0000000..53fd04a
--- /dev/null
+++ b/deps/hiredis/examples/example-libuv.c
@@ -0,0 +1,81 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+
+#include <hiredis.h>
+#include <async.h>
+#include <adapters/libuv.h>
+
+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) {
+ printf("`GET key` error: %s\n", c->errstr ? c->errstr : "unknown error");
+ return;
+ }
+ printf("`GET key` result: argv[%s]: %s\n", (char*)privdata, reply->str);
+
+ /* start another request that demonstrate timeout */
+ redisAsyncCommand(c, debugCallback, NULL, "DEBUG SLEEP %f", 1.5);
+}
+
+void connectCallback(const redisAsyncContext *c, int status) {
+ if (status != REDIS_OK) {
+ printf("connect error: %s\n", c->errstr);
+ return;
+ }
+ printf("Connected...\n");
+}
+
+void disconnectCallback(const redisAsyncContext *c, int status) {
+ if (status != REDIS_OK) {
+ printf("disconnect because of error: %s\n", c->errstr);
+ return;
+ }
+ printf("Disconnected...\n");
+}
+
+int main (int argc, char **argv) {
+#ifndef _WIN32
+ signal(SIGPIPE, SIG_IGN);
+#endif
+
+ uv_loop_t* loop = uv_default_loop();
+
+ redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379);
+ if (c->err) {
+ /* Let *c leak for now... */
+ printf("Error: %s\n", c->errstr);
+ return 1;
+ }
+
+ 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/deps/hiredis/examples/example-macosx.c b/deps/hiredis/examples/example-macosx.c
new file mode 100644
index 0000000..bc84ed5
--- /dev/null
+++ b/deps/hiredis/examples/example-macosx.c
@@ -0,0 +1,66 @@
+//
+// Created by Дмитрий Бахвалов on 13.07.15.
+// Copyright (c) 2015 Dmitry Bakhvalov. All rights reserved.
+//
+
+#include <stdio.h>
+
+#include <hiredis.h>
+#include <async.h>
+#include <adapters/macosx.h>
+
+void getCallback(redisAsyncContext *c, void *r, void *privdata) {
+ redisReply *reply = r;
+ if (reply == NULL) return;
+ printf("argv[%s]: %s\n", (char*)privdata, reply->str);
+
+ /* Disconnect after receiving the reply to GET */
+ redisAsyncDisconnect(c);
+}
+
+void connectCallback(const redisAsyncContext *c, int status) {
+ if (status != REDIS_OK) {
+ printf("Error: %s\n", c->errstr);
+ return;
+ }
+ printf("Connected...\n");
+}
+
+void disconnectCallback(const redisAsyncContext *c, int status) {
+ if (status != REDIS_OK) {
+ printf("Error: %s\n", c->errstr);
+ return;
+ }
+ CFRunLoopStop(CFRunLoopGetCurrent());
+ printf("Disconnected...\n");
+}
+
+int main (int argc, char **argv) {
+ signal(SIGPIPE, SIG_IGN);
+
+ CFRunLoopRef loop = CFRunLoopGetCurrent();
+ if( !loop ) {
+ printf("Error: Cannot get current run loop\n");
+ return 1;
+ }
+
+ redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379);
+ if (c->err) {
+ /* Let *c leak for now... */
+ printf("Error: %s\n", c->errstr);
+ return 1;
+ }
+
+ redisMacOSAttach(c, loop);
+
+ redisAsyncSetConnectCallback(c,connectCallback);
+ redisAsyncSetDisconnectCallback(c,disconnectCallback);
+
+ redisAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1]));
+ redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key");
+
+ CFRunLoopRun();
+
+ return 0;
+}
+
diff --git a/deps/hiredis/examples/example-push.c b/deps/hiredis/examples/example-push.c
new file mode 100644
index 0000000..6bc1205
--- /dev/null
+++ b/deps/hiredis/examples/example-push.c
@@ -0,0 +1,159 @@
+/*
+ * Copyright (c) 2020, Michael Grunder <michael dot grunder at gmail dot com>
+ *
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <hiredis.h>
+
+#define KEY_COUNT 5
+
+#define panicAbort(fmt, ...) \
+ do { \
+ fprintf(stderr, "%s:%d:%s(): " fmt, __FILE__, __LINE__, __func__, __VA_ARGS__); \
+ exit(-1); \
+ } while (0)
+
+static void assertReplyAndFree(redisContext *context, redisReply *reply, int type) {
+ if (reply == NULL)
+ panicAbort("NULL reply from server (error: %s)", context->errstr);
+
+ if (reply->type != type) {
+ if (reply->type == REDIS_REPLY_ERROR)
+ fprintf(stderr, "Redis Error: %s\n", reply->str);
+
+ panicAbort("Expected reply type %d but got type %d", type, reply->type);
+ }
+
+ freeReplyObject(reply);
+}
+
+/* Switch to the RESP3 protocol and enable client tracking */
+static void enableClientTracking(redisContext *c) {
+ redisReply *reply = redisCommand(c, "HELLO 3");
+ if (reply == NULL || c->err) {
+ panicAbort("NULL reply or server error (error: %s)", c->errstr);
+ }
+
+ if (reply->type != REDIS_REPLY_MAP) {
+ fprintf(stderr, "Error: Can't send HELLO 3 command. Are you sure you're ");
+ fprintf(stderr, "connected to redis-server >= 6.0.0?\nRedis error: %s\n",
+ reply->type == REDIS_REPLY_ERROR ? reply->str : "(unknown)");
+ exit(-1);
+ }
+
+ freeReplyObject(reply);
+
+ /* Enable client tracking */
+ reply = redisCommand(c, "CLIENT TRACKING ON");
+ assertReplyAndFree(c, reply, REDIS_REPLY_STATUS);
+}
+
+void pushReplyHandler(void *privdata, void *r) {
+ redisReply *reply = r;
+ int *invalidations = privdata;
+
+ /* Sanity check on the invalidation reply */
+ if (reply->type != REDIS_REPLY_PUSH || reply->elements != 2 ||
+ reply->element[1]->type != REDIS_REPLY_ARRAY ||
+ reply->element[1]->element[0]->type != REDIS_REPLY_STRING)
+ {
+ panicAbort("%s", "Can't parse PUSH message!");
+ }
+
+ /* Increment our invalidation count */
+ *invalidations += 1;
+
+ printf("pushReplyHandler(): INVALIDATE '%s' (invalidation count: %d)\n",
+ reply->element[1]->element[0]->str, *invalidations);
+
+ freeReplyObject(reply);
+}
+
+/* We aren't actually freeing anything here, but it is included to show that we can
+ * have hiredis call our data destructor when freeing the context */
+void privdata_dtor(void *privdata) {
+ unsigned int *icount = privdata;
+ printf("privdata_dtor(): In context privdata dtor (invalidations: %u)\n", *icount);
+}
+
+int main(int argc, char **argv) {
+ unsigned int j, invalidations = 0;
+ redisContext *c;
+ redisReply *reply;
+
+ const char *hostname = (argc > 1) ? argv[1] : "127.0.0.1";
+ int port = (argc > 2) ? atoi(argv[2]) : 6379;
+
+ redisOptions o = {0};
+ REDIS_OPTIONS_SET_TCP(&o, hostname, port);
+
+ /* Set our context privdata to the address of our invalidation counter. Each
+ * time our PUSH handler is called, hiredis will pass the privdata for context.
+ *
+ * This could also be done after we create the context like so:
+ *
+ * c->privdata = &invalidations;
+ * c->free_privdata = privdata_dtor;
+ */
+ REDIS_OPTIONS_SET_PRIVDATA(&o, &invalidations, privdata_dtor);
+
+ /* Set our custom PUSH message handler */
+ o.push_cb = pushReplyHandler;
+
+ c = redisConnectWithOptions(&o);
+ if (c == NULL || c->err)
+ panicAbort("Connection error: %s", c ? c->errstr : "OOM");
+
+ /* Enable RESP3 and turn on client tracking */
+ enableClientTracking(c);
+
+ /* Set some keys and then read them back. Once we do that, Redis will deliver
+ * invalidation push messages whenever the key is modified */
+ for (j = 0; j < KEY_COUNT; j++) {
+ reply = redisCommand(c, "SET key:%d initial:%d", j, j);
+ assertReplyAndFree(c, reply, REDIS_REPLY_STATUS);
+
+ reply = redisCommand(c, "GET key:%d", j);
+ assertReplyAndFree(c, reply, REDIS_REPLY_STRING);
+ }
+
+ /* Trigger invalidation messages by updating keys we just read */
+ for (j = 0; j < KEY_COUNT; j++) {
+ printf(" main(): SET key:%d update:%d\n", j, j);
+ reply = redisCommand(c, "SET key:%d update:%d", j, j);
+ assertReplyAndFree(c, reply, REDIS_REPLY_STATUS);
+ printf(" main(): SET REPLY OK\n");
+ }
+
+ printf("\nTotal detected invalidations: %d, expected: %d\n", invalidations, KEY_COUNT);
+
+ /* PING server */
+ redisFree(c);
+}
diff --git a/deps/hiredis/examples/example-qt.cpp b/deps/hiredis/examples/example-qt.cpp
new file mode 100644
index 0000000..f524c3f
--- /dev/null
+++ b/deps/hiredis/examples/example-qt.cpp
@@ -0,0 +1,46 @@
+#include <iostream>
+using namespace std;
+
+#include <QCoreApplication>
+#include <QTimer>
+
+#include "example-qt.h"
+
+void getCallback(redisAsyncContext *, void * r, void * privdata) {
+
+ redisReply * reply = static_cast<redisReply *>(r);
+ ExampleQt * ex = static_cast<ExampleQt *>(privdata);
+ if (reply == nullptr || ex == nullptr) return;
+
+ cout << "key: " << reply->str << endl;
+
+ ex->finish();
+}
+
+void ExampleQt::run() {
+
+ m_ctx = redisAsyncConnect("localhost", 6379);
+
+ if (m_ctx->err) {
+ cerr << "Error: " << m_ctx->errstr << endl;
+ redisAsyncFree(m_ctx);
+ emit finished();
+ }
+
+ m_adapter.setContext(m_ctx);
+
+ redisAsyncCommand(m_ctx, NULL, NULL, "SET key %s", m_value);
+ redisAsyncCommand(m_ctx, getCallback, this, "GET key");
+}
+
+int main (int argc, char **argv) {
+
+ QCoreApplication app(argc, argv);
+
+ ExampleQt example(argv[argc-1]);
+
+ QObject::connect(&example, SIGNAL(finished()), &app, SLOT(quit()));
+ QTimer::singleShot(0, &example, SLOT(run()));
+
+ return app.exec();
+}
diff --git a/deps/hiredis/examples/example-qt.h b/deps/hiredis/examples/example-qt.h
new file mode 100644
index 0000000..374f476
--- /dev/null
+++ b/deps/hiredis/examples/example-qt.h
@@ -0,0 +1,32 @@
+#ifndef __HIREDIS_EXAMPLE_QT_H
+#define __HIREDIS_EXAMPLE_QT_H
+
+#include <adapters/qt.h>
+
+class ExampleQt : public QObject {
+
+ Q_OBJECT
+
+ public:
+ ExampleQt(const char * value, QObject * parent = 0)
+ : QObject(parent), m_value(value) {}
+
+ signals:
+ void finished();
+
+ public slots:
+ void run();
+
+ private:
+ void finish() { emit finished(); }
+
+ private:
+ const char * m_value;
+ redisAsyncContext * m_ctx;
+ RedisQtAdapter m_adapter;
+
+ friend
+ void getCallback(redisAsyncContext *, void *, void *);
+};
+
+#endif /* !__HIREDIS_EXAMPLE_QT_H */
diff --git a/deps/hiredis/examples/example-ssl.c b/deps/hiredis/examples/example-ssl.c
new file mode 100644
index 0000000..b8ca442
--- /dev/null
+++ b/deps/hiredis/examples/example-ssl.c
@@ -0,0 +1,113 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <hiredis.h>
+#include <hiredis_ssl.h>
+
+#ifdef _MSC_VER
+#include <winsock2.h> /* For struct timeval */
+#endif
+
+int main(int argc, char **argv) {
+ unsigned int j;
+ redisSSLContext *ssl;
+ redisSSLContextError ssl_error;
+ redisContext *c;
+ redisReply *reply;
+ if (argc < 4) {
+ printf("Usage: %s <host> <port> <cert> <key> [ca]\n", argv[0]);
+ exit(1);
+ }
+ const char *hostname = (argc > 1) ? argv[1] : "127.0.0.1";
+ int port = atoi(argv[2]);
+ const char *cert = argv[3];
+ const char *key = argv[4];
+ const char *ca = argc > 4 ? argv[5] : NULL;
+
+ redisInitOpenSSL();
+ ssl = redisCreateSSLContext(ca, NULL, cert, key, NULL, &ssl_error);
+ if (!ssl) {
+ printf("SSL Context error: %s\n",
+ redisSSLContextGetError(ssl_error));
+ exit(1);
+ }
+
+ struct timeval tv = { 1, 500000 }; // 1.5 seconds
+ redisOptions options = {0};
+ REDIS_OPTIONS_SET_TCP(&options, hostname, port);
+ options.connect_timeout = &tv;
+ c = redisConnectWithOptions(&options);
+
+ if (c == NULL || c->err) {
+ if (c) {
+ printf("Connection error: %s\n", c->errstr);
+ redisFree(c);
+ } else {
+ printf("Connection error: can't allocate redis context\n");
+ }
+ exit(1);
+ }
+
+ if (redisInitiateSSLWithContext(c, ssl) != REDIS_OK) {
+ printf("Couldn't initialize SSL!\n");
+ printf("Error: %s\n", c->errstr);
+ redisFree(c);
+ exit(1);
+ }
+
+ /* PING server */
+ reply = redisCommand(c,"PING");
+ printf("PING: %s\n", reply->str);
+ freeReplyObject(reply);
+
+ /* Set a key */
+ reply = redisCommand(c,"SET %s %s", "foo", "hello world");
+ printf("SET: %s\n", reply->str);
+ freeReplyObject(reply);
+
+ /* Set a key using binary safe API */
+ reply = redisCommand(c,"SET %b %b", "bar", (size_t) 3, "hello", (size_t) 5);
+ printf("SET (binary API): %s\n", reply->str);
+ freeReplyObject(reply);
+
+ /* Try a GET and two INCR */
+ reply = redisCommand(c,"GET foo");
+ printf("GET foo: %s\n", reply->str);
+ freeReplyObject(reply);
+
+ reply = redisCommand(c,"INCR counter");
+ printf("INCR counter: %lld\n", reply->integer);
+ freeReplyObject(reply);
+ /* again ... */
+ reply = redisCommand(c,"INCR counter");
+ printf("INCR counter: %lld\n", reply->integer);
+ freeReplyObject(reply);
+
+ /* Create a list of numbers, from 0 to 9 */
+ reply = redisCommand(c,"DEL mylist");
+ freeReplyObject(reply);
+ for (j = 0; j < 10; j++) {
+ char buf[64];
+
+ snprintf(buf,64,"%u",j);
+ reply = redisCommand(c,"LPUSH mylist element-%s", buf);
+ freeReplyObject(reply);
+ }
+
+ /* Let's check what we have inside the list */
+ reply = redisCommand(c,"LRANGE mylist 0 -1");
+ if (reply->type == REDIS_REPLY_ARRAY) {
+ for (j = 0; j < reply->elements; j++) {
+ printf("%u) %s\n", j, reply->element[j]->str);
+ }
+ }
+ freeReplyObject(reply);
+
+ /* Disconnects and frees the context */
+ redisFree(c);
+
+ redisFreeSSLContext(ssl);
+
+ return 0;
+}
diff --git a/deps/hiredis/examples/example.c b/deps/hiredis/examples/example.c
new file mode 100644
index 0000000..f1b8b4a
--- /dev/null
+++ b/deps/hiredis/examples/example.c
@@ -0,0 +1,94 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <hiredis.h>
+
+#ifdef _MSC_VER
+#include <winsock2.h> /* For struct timeval */
+#endif
+
+int main(int argc, char **argv) {
+ unsigned int j, isunix = 0;
+ redisContext *c;
+ redisReply *reply;
+ const char *hostname = (argc > 1) ? argv[1] : "127.0.0.1";
+
+ if (argc > 2) {
+ if (*argv[2] == 'u' || *argv[2] == 'U') {
+ isunix = 1;
+ /* in this case, host is the path to the unix socket */
+ printf("Will connect to unix socket @%s\n", hostname);
+ }
+ }
+
+ int port = (argc > 2) ? atoi(argv[2]) : 6379;
+
+ struct timeval timeout = { 1, 500000 }; // 1.5 seconds
+ if (isunix) {
+ c = redisConnectUnixWithTimeout(hostname, timeout);
+ } else {
+ c = redisConnectWithTimeout(hostname, port, timeout);
+ }
+ if (c == NULL || c->err) {
+ if (c) {
+ printf("Connection error: %s\n", c->errstr);
+ redisFree(c);
+ } else {
+ printf("Connection error: can't allocate redis context\n");
+ }
+ exit(1);
+ }
+
+ /* PING server */
+ reply = redisCommand(c,"PING");
+ printf("PING: %s\n", reply->str);
+ freeReplyObject(reply);
+
+ /* Set a key */
+ reply = redisCommand(c,"SET %s %s", "foo", "hello world");
+ printf("SET: %s\n", reply->str);
+ freeReplyObject(reply);
+
+ /* Set a key using binary safe API */
+ reply = redisCommand(c,"SET %b %b", "bar", (size_t) 3, "hello", (size_t) 5);
+ printf("SET (binary API): %s\n", reply->str);
+ freeReplyObject(reply);
+
+ /* Try a GET and two INCR */
+ reply = redisCommand(c,"GET foo");
+ printf("GET foo: %s\n", reply->str);
+ freeReplyObject(reply);
+
+ reply = redisCommand(c,"INCR counter");
+ printf("INCR counter: %lld\n", reply->integer);
+ freeReplyObject(reply);
+ /* again ... */
+ reply = redisCommand(c,"INCR counter");
+ printf("INCR counter: %lld\n", reply->integer);
+ freeReplyObject(reply);
+
+ /* Create a list of numbers, from 0 to 9 */
+ reply = redisCommand(c,"DEL mylist");
+ freeReplyObject(reply);
+ for (j = 0; j < 10; j++) {
+ char buf[64];
+
+ snprintf(buf,64,"%u",j);
+ reply = redisCommand(c,"LPUSH mylist element-%s", buf);
+ freeReplyObject(reply);
+ }
+
+ /* Let's check what we have inside the list */
+ reply = redisCommand(c,"LRANGE mylist 0 -1");
+ if (reply->type == REDIS_REPLY_ARRAY) {
+ for (j = 0; j < reply->elements; j++) {
+ printf("%u) %s\n", j, reply->element[j]->str);
+ }
+ }
+ freeReplyObject(reply);
+
+ /* Disconnects and frees the context */
+ redisFree(c);
+
+ return 0;
+}
diff --git a/deps/hiredis/fmacros.h b/deps/hiredis/fmacros.h
new file mode 100644
index 0000000..754a53c
--- /dev/null
+++ b/deps/hiredis/fmacros.h
@@ -0,0 +1,14 @@
+#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 */
+#define _DARWIN_C_SOURCE
+#endif
+
+#endif
diff --git a/deps/hiredis/fuzzing/format_command_fuzzer.c b/deps/hiredis/fuzzing/format_command_fuzzer.c
new file mode 100644
index 0000000..91adeac
--- /dev/null
+++ b/deps/hiredis/fuzzing/format_command_fuzzer.c
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2020, Salvatore Sanfilippo <antirez at gmail dot com>
+ * Copyright (c) 2020, Pieter Noordhuis <pcnoordhuis at gmail dot com>
+ * Copyright (c) 2020, Matt Stancliff <matt at genges dot com>,
+ * Jan-Erik Rediger <janerik at fnordig dot com>
+ *
+ * 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 <stdlib.h>
+#include <string.h>
+#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/deps/hiredis/hiredis-config.cmake.in b/deps/hiredis/hiredis-config.cmake.in
new file mode 100644
index 0000000..98851dc
--- /dev/null
+++ b/deps/hiredis/hiredis-config.cmake.in
@@ -0,0 +1,13 @@
+@PACKAGE_INIT@
+
+set_and_check(hiredis_INCLUDEDIR "@PACKAGE_INCLUDE_INSTALL_DIR@")
+
+IF (NOT TARGET hiredis::hiredis)
+ INCLUDE(${CMAKE_CURRENT_LIST_DIR}/hiredis-targets.cmake)
+ENDIF()
+
+SET(hiredis_LIBRARIES hiredis::hiredis)
+SET(hiredis_INCLUDE_DIRS ${hiredis_INCLUDEDIR})
+
+check_required_components(hiredis)
+
diff --git a/deps/hiredis/hiredis.c b/deps/hiredis/hiredis.c
new file mode 100644
index 0000000..f7b61c2
--- /dev/null
+++ b/deps/hiredis/hiredis.c
@@ -0,0 +1,1188 @@
+/*
+ * Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com>
+ * Copyright (c) 2010-2014, Pieter Noordhuis <pcnoordhuis at gmail dot com>
+ * Copyright (c) 2015, Matt Stancliff <matt at genges dot com>,
+ * Jan-Erik Rediger <janerik at fnordig dot com>
+ *
+ * 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 "fmacros.h"
+#include <string.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <errno.h>
+#include <ctype.h>
+
+#include "hiredis.h"
+#include "net.h"
+#include "sds.h"
+#include "async.h"
+#include "win32.h"
+
+extern int redisContextUpdateConnectTimeout(redisContext *c, const struct timeval *timeout);
+extern int redisContextUpdateCommandTimeout(redisContext *c, const struct timeval *timeout);
+
+static redisContextFuncs redisContextDefaultFuncs = {
+ .free_privctx = NULL,
+ .async_read = redisAsyncRead,
+ .async_write = redisAsyncWrite,
+ .read = redisNetRead,
+ .write = redisNetWrite
+};
+
+static redisReply *createReplyObject(int type);
+static void *createStringObject(const redisReadTask *task, char *str, size_t len);
+static void *createArrayObject(const redisReadTask *task, size_t elements);
+static void *createIntegerObject(const redisReadTask *task, long long value);
+static void *createDoubleObject(const redisReadTask *task, double value, char *str, size_t len);
+static void *createNilObject(const redisReadTask *task);
+static void *createBoolObject(const redisReadTask *task, int bval);
+
+/* Default set of functions to build the reply. Keep in mind that such a
+ * function returning NULL is interpreted as OOM. */
+static redisReplyObjectFunctions defaultFunctions = {
+ createStringObject,
+ createArrayObject,
+ createIntegerObject,
+ createDoubleObject,
+ createNilObject,
+ createBoolObject,
+ freeReplyObject
+};
+
+/* Create a reply object */
+static redisReply *createReplyObject(int type) {
+ redisReply *r = hi_calloc(1,sizeof(*r));
+
+ if (r == NULL)
+ return NULL;
+
+ r->type = type;
+ return r;
+}
+
+/* Free a reply object */
+void freeReplyObject(void *reply) {
+ redisReply *r = reply;
+ size_t j;
+
+ if (r == NULL)
+ return;
+
+ 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:
+ case REDIS_REPLY_SET:
+ case REDIS_REPLY_PUSH:
+ if (r->element != NULL) {
+ for (j = 0; j < r->elements; j++)
+ freeReplyObject(r->element[j]);
+ hi_free(r->element);
+ }
+ break;
+ case REDIS_REPLY_ERROR:
+ case REDIS_REPLY_STATUS:
+ case REDIS_REPLY_STRING:
+ case REDIS_REPLY_DOUBLE:
+ case REDIS_REPLY_VERB:
+ case REDIS_REPLY_BIGNUM:
+ hi_free(r->str);
+ break;
+ }
+ hi_free(r);
+}
+
+static void *createStringObject(const redisReadTask *task, char *str, size_t len) {
+ redisReply *r, *parent;
+ char *buf;
+
+ r = createReplyObject(task->type);
+ if (r == NULL)
+ return NULL;
+
+ 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_BIGNUM);
+
+ /* Copy string value */
+ if (task->type == REDIS_REPLY_VERB) {
+ buf = hi_malloc(len-4+1); /* Skip 4 bytes of verbatim type header. */
+ if (buf == NULL) goto oom;
+
+ memcpy(r->vtype,str,3);
+ r->vtype[3] = '\0';
+ memcpy(buf,str+4,len-4);
+ buf[len-4] = '\0';
+ r->len = len - 4;
+ } else {
+ buf = hi_malloc(len+1);
+ if (buf == NULL) goto oom;
+
+ memcpy(buf,str,len);
+ buf[len] = '\0';
+ r->len = len;
+ }
+ r->str = buf;
+
+ if (task->parent) {
+ parent = task->parent->obj;
+ assert(parent->type == REDIS_REPLY_ARRAY ||
+ parent->type == REDIS_REPLY_MAP ||
+ parent->type == REDIS_REPLY_SET ||
+ parent->type == REDIS_REPLY_PUSH);
+ parent->element[task->idx] = r;
+ }
+ return r;
+
+oom:
+ freeReplyObject(r);
+ return NULL;
+}
+
+static void *createArrayObject(const redisReadTask *task, size_t elements) {
+ redisReply *r, *parent;
+
+ r = createReplyObject(task->type);
+ if (r == NULL)
+ return NULL;
+
+ if (elements > 0) {
+ r->element = hi_calloc(elements,sizeof(redisReply*));
+ if (r->element == NULL) {
+ freeReplyObject(r);
+ return NULL;
+ }
+ }
+
+ r->elements = elements;
+
+ 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_PUSH);
+ parent->element[task->idx] = r;
+ }
+ return r;
+}
+
+static void *createIntegerObject(const redisReadTask *task, long long value) {
+ redisReply *r, *parent;
+
+ r = createReplyObject(REDIS_REPLY_INTEGER);
+ if (r == NULL)
+ return NULL;
+
+ r->integer = value;
+
+ 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_PUSH);
+ parent->element[task->idx] = r;
+ }
+ return r;
+}
+
+static void *createDoubleObject(const redisReadTask *task, double value, char *str, size_t len) {
+ redisReply *r, *parent;
+
+ r = createReplyObject(REDIS_REPLY_DOUBLE);
+ if (r == NULL)
+ return NULL;
+
+ r->dval = value;
+ r->str = hi_malloc(len+1);
+ if (r->str == NULL) {
+ freeReplyObject(r);
+ return NULL;
+ }
+
+ /* The double reply also has the original protocol string representing a
+ * double as a null terminated string. This way the caller does not need
+ * to format back for string conversion, especially since Redis does efforts
+ * to make the string more human readable avoiding the calssical double
+ * decimal string conversion artifacts. */
+ memcpy(r->str, str, len);
+ r->str[len] = '\0';
+ r->len = len;
+
+ if (task->parent) {
+ parent = task->parent->obj;
+ assert(parent->type == REDIS_REPLY_ARRAY ||
+ parent->type == REDIS_REPLY_MAP ||
+ parent->type == REDIS_REPLY_SET ||
+ parent->type == REDIS_REPLY_PUSH);
+ parent->element[task->idx] = r;
+ }
+ return r;
+}
+
+static void *createNilObject(const redisReadTask *task) {
+ redisReply *r, *parent;
+
+ r = createReplyObject(REDIS_REPLY_NIL);
+ if (r == NULL)
+ return NULL;
+
+ 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_PUSH);
+ parent->element[task->idx] = r;
+ }
+ return r;
+}
+
+static void *createBoolObject(const redisReadTask *task, int bval) {
+ redisReply *r, *parent;
+
+ r = createReplyObject(REDIS_REPLY_BOOL);
+ if (r == NULL)
+ return NULL;
+
+ r->integer = bval != 0;
+
+ 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_PUSH);
+ parent->element[task->idx] = r;
+ }
+ return r;
+}
+
+/* Return the number of digits of 'v' when converted to string in radix 10.
+ * Implementation borrowed from link in redis/src/util.c:string2ll(). */
+static uint32_t countDigits(uint64_t v) {
+ uint32_t result = 1;
+ for (;;) {
+ if (v < 10) return result;
+ if (v < 100) return result + 1;
+ if (v < 1000) return result + 2;
+ if (v < 10000) return result + 3;
+ v /= 10000U;
+ result += 4;
+ }
+}
+
+/* Helper that calculates the bulk length given a certain string length. */
+static size_t bulklen(size_t len) {
+ return 1+countDigits(len)+2+len+2;
+}
+
+int 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 */
+ int touched = 0; /* was the current argument touched? */
+ char **curargv = NULL, **newargv = NULL;
+ int argc = 0;
+ int totlen = 0;
+ int error_type = 0; /* 0 = no error; -1 = memory error; -2 = format error */
+ int j;
+
+ /* Abort if there is not target to set */
+ if (target == NULL)
+ return -1;
+
+ /* Build the command string accordingly to protocol */
+ curarg = hi_sdsempty();
+ if (curarg == NULL)
+ return -1;
+
+ while(*c != '\0') {
+ if (*c != '%' || c[1] == '\0') {
+ if (*c == ' ') {
+ if (touched) {
+ newargv = hi_realloc(curargv,sizeof(char*)*(argc+1));
+ if (newargv == NULL) goto memory_err;
+ curargv = newargv;
+ curargv[argc++] = curarg;
+ totlen += bulklen(hi_sdslen(curarg));
+
+ /* curarg is put in argv so it can be overwritten. */
+ curarg = hi_sdsempty();
+ if (curarg == NULL) goto memory_err;
+ touched = 0;
+ }
+ } else {
+ newarg = hi_sdscatlen(curarg,c,1);
+ if (newarg == NULL) goto memory_err;
+ curarg = newarg;
+ touched = 1;
+ }
+ } else {
+ char *arg;
+ size_t size;
+
+ /* Set newarg so it can be checked even if it is not touched. */
+ newarg = curarg;
+
+ switch(c[1]) {
+ case 's':
+ arg = va_arg(ap,char*);
+ size = strlen(arg);
+ if (size > 0)
+ newarg = hi_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);
+ break;
+ case '%':
+ newarg = hi_sdscat(curarg,"%");
+ break;
+ default:
+ /* Try to detect printf format */
+ {
+ static const char intfmts[] = "diouxX";
+ static const char flags[] = "#0-+ ";
+ char _format[16];
+ const char *_p = c+1;
+ size_t _l = 0;
+ va_list _cpy;
+
+ /* Flags */
+ while (*_p != '\0' && strchr(flags,*_p) != NULL) _p++;
+
+ /* Field width */
+ while (*_p != '\0' && isdigit(*_p)) _p++;
+
+ /* Precision */
+ if (*_p == '.') {
+ _p++;
+ while (*_p != '\0' && isdigit(*_p)) _p++;
+ }
+
+ /* Copy va_list before consuming with va_arg */
+ va_copy(_cpy,ap);
+
+ /* Integer conversion (without modifiers) */
+ if (strchr(intfmts,*_p) != NULL) {
+ va_arg(ap,int);
+ goto fmt_valid;
+ }
+
+ /* Double conversion (without modifiers) */
+ if (strchr("eEfFgGaA",*_p) != NULL) {
+ va_arg(ap,double);
+ goto fmt_valid;
+ }
+
+ /* Size: char */
+ if (_p[0] == 'h' && _p[1] == 'h') {
+ _p += 2;
+ if (*_p != '\0' && strchr(intfmts,*_p) != NULL) {
+ va_arg(ap,int); /* char gets promoted to int */
+ goto fmt_valid;
+ }
+ goto fmt_invalid;
+ }
+
+ /* Size: short */
+ if (_p[0] == 'h') {
+ _p += 1;
+ if (*_p != '\0' && strchr(intfmts,*_p) != NULL) {
+ va_arg(ap,int); /* short gets promoted to int */
+ goto fmt_valid;
+ }
+ goto fmt_invalid;
+ }
+
+ /* Size: long long */
+ if (_p[0] == 'l' && _p[1] == 'l') {
+ _p += 2;
+ if (*_p != '\0' && strchr(intfmts,*_p) != NULL) {
+ va_arg(ap,long long);
+ goto fmt_valid;
+ }
+ goto fmt_invalid;
+ }
+
+ /* Size: long */
+ if (_p[0] == 'l') {
+ _p += 1;
+ if (*_p != '\0' && strchr(intfmts,*_p) != NULL) {
+ va_arg(ap,long);
+ goto fmt_valid;
+ }
+ goto fmt_invalid;
+ }
+
+ fmt_invalid:
+ va_end(_cpy);
+ goto format_err;
+
+ fmt_valid:
+ _l = (_p+1)-c;
+ if (_l < sizeof(_format)-2) {
+ memcpy(_format,c,_l);
+ _format[_l] = '\0';
+ newarg = hi_sdscatvprintf(curarg,_format,_cpy);
+
+ /* Update current position (note: outer blocks
+ * increment c twice so compensate here) */
+ c = _p-1;
+ }
+
+ va_end(_cpy);
+ break;
+ }
+ }
+
+ if (newarg == NULL) goto memory_err;
+ curarg = newarg;
+
+ touched = 1;
+ c++;
+ }
+ c++;
+ }
+
+ /* Add the last argument if needed */
+ if (touched) {
+ newargv = hi_realloc(curargv,sizeof(char*)*(argc+1));
+ if (newargv == NULL) goto memory_err;
+ curargv = newargv;
+ curargv[argc++] = curarg;
+ totlen += bulklen(hi_sdslen(curarg));
+ } else {
+ hi_sdsfree(curarg);
+ }
+
+ /* Clear curarg because it was put in curargv or was free'd. */
+ curarg = NULL;
+
+ /* Add bytes needed to hold multi bulk count */
+ totlen += 1+countDigits(argc)+2;
+
+ /* Build the command at protocol level */
+ cmd = hi_malloc(totlen+1);
+ if (cmd == NULL) goto memory_err;
+
+ pos = sprintf(cmd,"*%d\r\n",argc);
+ for (j = 0; j < argc; j++) {
+ pos += sprintf(cmd+pos,"$%zu\r\n",hi_sdslen(curargv[j]));
+ memcpy(cmd+pos,curargv[j],hi_sdslen(curargv[j]));
+ pos += hi_sdslen(curargv[j]);
+ hi_sdsfree(curargv[j]);
+ cmd[pos++] = '\r';
+ cmd[pos++] = '\n';
+ }
+ assert(pos == totlen);
+ cmd[pos] = '\0';
+
+ hi_free(curargv);
+ *target = cmd;
+ return totlen;
+
+format_err:
+ error_type = -2;
+ goto cleanup;
+
+memory_err:
+ error_type = -1;
+ goto cleanup;
+
+cleanup:
+ if (curargv) {
+ while(argc--)
+ hi_sdsfree(curargv[argc]);
+ hi_free(curargv);
+ }
+
+ hi_sdsfree(curarg);
+ hi_free(cmd);
+
+ return error_type;
+}
+
+/* Format a command according to the Redis protocol. This function
+ * takes a format similar to printf:
+ *
+ * %s represents a C null terminated string you want to interpolate
+ * %b represents a binary safe string
+ *
+ * When using %b you need to provide both the pointer to the string
+ * and the length in bytes as a size_t. Examples:
+ *
+ * len = redisFormatCommand(target, "GET %s", mykey);
+ * len = redisFormatCommand(target, "SET %s %b", mykey, myval, myvallen);
+ */
+int redisFormatCommand(char **target, const char *format, ...) {
+ va_list ap;
+ int len;
+ va_start(ap,format);
+ len = redisvFormatCommand(target,format,ap);
+ va_end(ap);
+
+ /* The API says "-1" means bad result, but we now also return "-2" in some
+ * cases. Force the return value to always be -1. */
+ if (len < 0)
+ len = -1;
+
+ return len;
+}
+
+/* Format a command according to the Redis protocol using an hisds string and
+ * hi_sdscatfmt for the processing of arguments. This function takes the
+ * number of arguments, an array with arguments and an array with their
+ * lengths. If the latter is set to NULL, strlen will be used to compute the
+ * argument lengths.
+ */
+long long redisFormatSdsCommandArgv(hisds *target, int argc, const char **argv,
+ const size_t *argvlen)
+{
+ hisds cmd, aux;
+ unsigned long long totlen, len;
+ int j;
+
+ /* Abort on a NULL target */
+ if (target == NULL)
+ return -1;
+
+ /* Calculate our total size */
+ totlen = 1+countDigits(argc)+2;
+ for (j = 0; j < argc; j++) {
+ len = argvlen ? argvlen[j] : strlen(argv[j]);
+ totlen += bulklen(len);
+ }
+
+ /* Use an SDS string for command construction */
+ cmd = hi_sdsempty();
+ if (cmd == NULL)
+ return -1;
+
+ /* We already know how much storage we need */
+ aux = hi_sdsMakeRoomFor(cmd, totlen);
+ if (aux == NULL) {
+ hi_sdsfree(cmd);
+ return -1;
+ }
+
+ cmd = aux;
+
+ /* Construct command */
+ cmd = hi_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);
+ }
+
+ assert(hi_sdslen(cmd)==totlen);
+
+ *target = cmd;
+ return totlen;
+}
+
+void redisFreeSdsCommand(hisds cmd) {
+ hi_sdsfree(cmd);
+}
+
+/* Format a command according to the Redis protocol. This function takes the
+ * number of arguments, an array with arguments and an array with their
+ * lengths. If the latter is set to NULL, strlen will be used to compute the
+ * argument lengths.
+ */
+long long redisFormatCommandArgv(char **target, int argc, const char **argv, const size_t *argvlen) {
+ char *cmd = NULL; /* final command */
+ size_t pos; /* position in final command */
+ size_t len, totlen;
+ int j;
+
+ /* Abort on a NULL target */
+ if (target == NULL)
+ return -1;
+
+ /* Calculate number of bytes needed for the command */
+ totlen = 1+countDigits(argc)+2;
+ for (j = 0; j < argc; j++) {
+ len = argvlen ? argvlen[j] : strlen(argv[j]);
+ totlen += bulklen(len);
+ }
+
+ /* Build the command at protocol level */
+ cmd = hi_malloc(totlen+1);
+ if (cmd == NULL)
+ return -1;
+
+ pos = sprintf(cmd,"*%d\r\n",argc);
+ for (j = 0; j < argc; j++) {
+ len = argvlen ? argvlen[j] : strlen(argv[j]);
+ pos += sprintf(cmd+pos,"$%zu\r\n",len);
+ memcpy(cmd+pos,argv[j],len);
+ pos += len;
+ cmd[pos++] = '\r';
+ cmd[pos++] = '\n';
+ }
+ assert(pos == totlen);
+ cmd[pos] = '\0';
+
+ *target = cmd;
+ return totlen;
+}
+
+void redisFreeCommand(char *cmd) {
+ hi_free(cmd);
+}
+
+void __redisSetError(redisContext *c, int type, const char *str) {
+ size_t len;
+
+ c->err = type;
+ if (str != NULL) {
+ len = strlen(str);
+ len = len < (sizeof(c->errstr)-1) ? len : (sizeof(c->errstr)-1);
+ memcpy(c->errstr,str,len);
+ c->errstr[len] = '\0';
+ } else {
+ /* Only REDIS_ERR_IO may lack a description! */
+ assert(type == REDIS_ERR_IO);
+ strerror_r(errno, c->errstr, sizeof(c->errstr));
+ }
+}
+
+redisReader *redisReaderCreate(void) {
+ return redisReaderCreateWithFunctions(&defaultFunctions);
+}
+
+static void redisPushAutoFree(void *privdata, void *reply) {
+ (void)privdata;
+ freeReplyObject(reply);
+}
+
+static redisContext *redisContextInit(void) {
+ redisContext *c;
+
+ c = hi_calloc(1, sizeof(*c));
+ if (c == NULL)
+ return NULL;
+
+ c->funcs = &redisContextDefaultFuncs;
+
+ c->obuf = hi_sdsempty();
+ c->reader = redisReaderCreate();
+ c->fd = REDIS_INVALID_FD;
+
+ if (c->obuf == NULL || c->reader == NULL) {
+ redisFree(c);
+ return NULL;
+ }
+
+ return c;
+}
+
+void redisFree(redisContext *c) {
+ if (c == NULL)
+ return;
+ redisNetClose(c);
+
+ hi_sdsfree(c->obuf);
+ redisReaderFree(c->reader);
+ hi_free(c->tcp.host);
+ hi_free(c->tcp.source_addr);
+ hi_free(c->unix_sock.path);
+ hi_free(c->connect_timeout);
+ hi_free(c->command_timeout);
+ hi_free(c->saddr);
+
+ if (c->privdata && c->free_privdata)
+ c->free_privdata(c->privdata);
+
+ if (c->funcs->free_privctx)
+ c->funcs->free_privctx(c->privctx);
+
+ memset(c, 0xff, sizeof(*c));
+ hi_free(c);
+}
+
+redisFD redisFreeKeepFd(redisContext *c) {
+ redisFD fd = c->fd;
+ c->fd = REDIS_INVALID_FD;
+ redisFree(c);
+ return fd;
+}
+
+int redisReconnect(redisContext *c) {
+ c->err = 0;
+ memset(c->errstr, '\0', strlen(c->errstr));
+
+ if (c->privctx && c->funcs->free_privctx) {
+ c->funcs->free_privctx(c->privctx);
+ c->privctx = NULL;
+ }
+
+ redisNetClose(c);
+
+ hi_sdsfree(c->obuf);
+ redisReaderFree(c->reader);
+
+ c->obuf = hi_sdsempty();
+ c->reader = redisReaderCreate();
+
+ if (c->obuf == NULL || c->reader == NULL) {
+ __redisSetError(c, REDIS_ERR_OOM, "Out of memory");
+ return REDIS_ERR;
+ }
+
+ int ret = REDIS_ERR;
+ if (c->connection_type == REDIS_CONN_TCP) {
+ ret = redisContextConnectBindTcp(c, c->tcp.host, c->tcp.port,
+ c->connect_timeout, c->tcp.source_addr);
+ } else if (c->connection_type == REDIS_CONN_UNIX) {
+ ret = redisContextConnectUnix(c, c->unix_sock.path, c->connect_timeout);
+ } else {
+ /* Something bad happened here and shouldn't have. There isn't
+ enough information in the context to reconnect. */
+ __redisSetError(c,REDIS_ERR_OTHER,"Not enough information to reconnect");
+ ret = REDIS_ERR;
+ }
+
+ if (c->command_timeout != NULL && (c->flags & REDIS_BLOCK) && c->fd != REDIS_INVALID_FD) {
+ redisContextSetTimeout(c, *c->command_timeout);
+ }
+
+ return ret;
+}
+
+redisContext *redisConnectWithOptions(const redisOptions *options) {
+ redisContext *c = redisContextInit();
+ if (c == NULL) {
+ return NULL;
+ }
+ if (!(options->options & REDIS_OPT_NONBLOCK)) {
+ c->flags |= REDIS_BLOCK;
+ }
+ if (options->options & REDIS_OPT_REUSEADDR) {
+ c->flags |= REDIS_REUSEADDR;
+ }
+ 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. */
+ if (options->push_cb != NULL)
+ redisSetPushCallback(c, options->push_cb);
+ else if (!(options->options & REDIS_OPT_NO_PUSH_AUTOFREE))
+ redisSetPushCallback(c, redisPushAutoFree);
+
+ c->privdata = options->privdata;
+ c->free_privdata = options->free_privdata;
+
+ if (redisContextUpdateConnectTimeout(c, options->connect_timeout) != REDIS_OK ||
+ redisContextUpdateCommandTimeout(c, options->command_timeout) != REDIS_OK) {
+ __redisSetError(c, REDIS_ERR_OOM, "Out of memory");
+ return c;
+ }
+
+ if (options->type == REDIS_CONN_TCP) {
+ redisContextConnectBindTcp(c, options->endpoint.tcp.ip,
+ options->endpoint.tcp.port, options->connect_timeout,
+ options->endpoint.tcp.source_addr);
+ } else if (options->type == REDIS_CONN_UNIX) {
+ redisContextConnectUnix(c, options->endpoint.unix_socket,
+ options->connect_timeout);
+ } else if (options->type == REDIS_CONN_USERFD) {
+ c->fd = options->endpoint.fd;
+ c->flags |= REDIS_CONNECTED;
+ } else {
+ redisFree(c);
+ return NULL;
+ }
+
+ if (options->command_timeout != NULL && (c->flags & REDIS_BLOCK) && c->fd != REDIS_INVALID_FD) {
+ redisContextSetTimeout(c, *options->command_timeout);
+ }
+
+ return c;
+}
+
+/* Connect to a Redis instance. On error the field error in the returned
+ * context will be set to the return value of the error function.
+ * When no set of reply functions is given, the default set will be used. */
+redisContext *redisConnect(const char *ip, int port) {
+ redisOptions options = {0};
+ REDIS_OPTIONS_SET_TCP(&options, ip, port);
+ return redisConnectWithOptions(&options);
+}
+
+redisContext *redisConnectWithTimeout(const char *ip, int port, const struct timeval tv) {
+ redisOptions options = {0};
+ REDIS_OPTIONS_SET_TCP(&options, ip, port);
+ options.connect_timeout = &tv;
+ return redisConnectWithOptions(&options);
+}
+
+redisContext *redisConnectNonBlock(const char *ip, int port) {
+ redisOptions options = {0};
+ REDIS_OPTIONS_SET_TCP(&options, ip, port);
+ options.options |= REDIS_OPT_NONBLOCK;
+ return redisConnectWithOptions(&options);
+}
+
+redisContext *redisConnectBindNonBlock(const char *ip, int port,
+ const char *source_addr) {
+ redisOptions options = {0};
+ REDIS_OPTIONS_SET_TCP(&options, ip, port);
+ options.endpoint.tcp.source_addr = source_addr;
+ options.options |= REDIS_OPT_NONBLOCK;
+ return redisConnectWithOptions(&options);
+}
+
+redisContext *redisConnectBindNonBlockWithReuse(const char *ip, int port,
+ const char *source_addr) {
+ redisOptions options = {0};
+ REDIS_OPTIONS_SET_TCP(&options, ip, port);
+ options.endpoint.tcp.source_addr = source_addr;
+ options.options |= REDIS_OPT_NONBLOCK|REDIS_OPT_REUSEADDR;
+ return redisConnectWithOptions(&options);
+}
+
+redisContext *redisConnectUnix(const char *path) {
+ redisOptions options = {0};
+ REDIS_OPTIONS_SET_UNIX(&options, path);
+ return redisConnectWithOptions(&options);
+}
+
+redisContext *redisConnectUnixWithTimeout(const char *path, const struct timeval tv) {
+ redisOptions options = {0};
+ REDIS_OPTIONS_SET_UNIX(&options, path);
+ options.connect_timeout = &tv;
+ return redisConnectWithOptions(&options);
+}
+
+redisContext *redisConnectUnixNonBlock(const char *path) {
+ redisOptions options = {0};
+ REDIS_OPTIONS_SET_UNIX(&options, path);
+ options.options |= REDIS_OPT_NONBLOCK;
+ return redisConnectWithOptions(&options);
+}
+
+redisContext *redisConnectFd(redisFD fd) {
+ redisOptions options = {0};
+ options.type = REDIS_CONN_USERFD;
+ options.endpoint.fd = fd;
+ return redisConnectWithOptions(&options);
+}
+
+/* Set read/write timeout on a blocking socket. */
+int redisSetTimeout(redisContext *c, const struct timeval tv) {
+ if (c->flags & REDIS_BLOCK)
+ return redisContextSetTimeout(c,tv);
+ return REDIS_ERR;
+}
+
+/* Enable connection KeepAlive. */
+int redisEnableKeepAlive(redisContext *c) {
+ if (redisKeepAlive(c, REDIS_KEEPALIVE_INTERVAL) != REDIS_OK)
+ return REDIS_ERR;
+ return REDIS_OK;
+}
+
+/* Set a user provided RESP3 PUSH handler and return any old one set. */
+redisPushFn *redisSetPushCallback(redisContext *c, redisPushFn *fn) {
+ redisPushFn *old = c->push_cb;
+ c->push_cb = fn;
+ return old;
+}
+
+/* Use this function to handle a read event on the descriptor. It will try
+ * and read some bytes from the socket and feed them to the reply parser.
+ *
+ * After this function is called, you may use redisGetReplyFromReader to
+ * see if there is a reply available. */
+int redisBufferRead(redisContext *c) {
+ char buf[1024*16];
+ int nread;
+
+ /* Return early when the context has seen an error. */
+ if (c->err)
+ return REDIS_ERR;
+
+ nread = c->funcs->read(c, buf, sizeof(buf));
+ 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;
+}
+
+/* Write the output buffer to the socket.
+ *
+ * Returns REDIS_OK when the buffer is empty, or (a part of) the buffer was
+ * successfully written to the socket. When the buffer is empty after the
+ * write operation, "done" is set to 1 (if given).
+ *
+ * Returns REDIS_ERR if an error occurred trying to write and sets
+ * c->errstr to hold the appropriate error string.
+ */
+int redisBufferWrite(redisContext *c, int *done) {
+
+ /* Return early when the context has seen an error. */
+ if (c->err)
+ return REDIS_ERR;
+
+ if (hi_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 (c->obuf == NULL)
+ goto oom;
+ } else {
+ if (hi_sdsrange(c->obuf,nwritten,-1) < 0) goto oom;
+ }
+ }
+ }
+ if (done != NULL) *done = (hi_sdslen(c->obuf) == 0);
+ return REDIS_OK;
+
+oom:
+ __redisSetError(c, REDIS_ERR_OOM, "Out of memory");
+ return REDIS_ERR;
+}
+
+/* Internal helper that returns 1 if the reply was a RESP3 PUSH
+ * message and we handled it with a user-provided callback. */
+static int redisHandledPushReply(redisContext *c, void *reply) {
+ if (reply && c->push_cb && redisIsPushReply(reply)) {
+ c->push_cb(c->privdata, reply);
+ return 1;
+ }
+
+ return 0;
+}
+
+/* Get a reply from our reader or set an error in the context. */
+int 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 (redisNextInBandReplyFromReader(c,&aux) == REDIS_ERR)
+ return REDIS_ERR;
+
+ /* For the blocking context, flush output buffer and read reply */
+ if (aux == NULL && c->flags & REDIS_BLOCK) {
+ /* Write until done */
+ do {
+ if (redisBufferWrite(c,&wdone) == REDIS_ERR)
+ return REDIS_ERR;
+ } while (!wdone);
+
+ /* Read until there is a reply */
+ do {
+ if (redisBufferRead(c) == REDIS_ERR)
+ return REDIS_ERR;
+
+ if (redisNextInBandReplyFromReader(c,&aux) == REDIS_ERR)
+ return REDIS_ERR;
+ } while (aux == NULL);
+ }
+
+ /* Set reply or free it if we were passed NULL */
+ if (reply != NULL) {
+ *reply = aux;
+ } else {
+ freeReplyObject(aux);
+ }
+
+ return REDIS_OK;
+}
+
+
+/* Helper function for the redisAppendCommand* family of functions.
+ *
+ * Write a formatted command to the output buffer. When this family
+ * is used, you need to call redisGetReply yourself to retrieve
+ * the reply (or replies in pub/sub).
+ */
+int __redisAppendCommand(redisContext *c, const char *cmd, size_t len) {
+ hisds newbuf;
+
+ newbuf = hi_sdscatlen(c->obuf,cmd,len);
+ if (newbuf == NULL) {
+ __redisSetError(c,REDIS_ERR_OOM,"Out of memory");
+ return REDIS_ERR;
+ }
+
+ c->obuf = newbuf;
+ return REDIS_OK;
+}
+
+int redisAppendFormattedCommand(redisContext *c, const char *cmd, size_t len) {
+
+ if (__redisAppendCommand(c, cmd, len) != REDIS_OK) {
+ return REDIS_ERR;
+ }
+
+ return REDIS_OK;
+}
+
+int redisvAppendCommand(redisContext *c, const char *format, va_list ap) {
+ char *cmd;
+ int len;
+
+ len = redisvFormatCommand(&cmd,format,ap);
+ if (len == -1) {
+ __redisSetError(c,REDIS_ERR_OOM,"Out of memory");
+ return REDIS_ERR;
+ } else if (len == -2) {
+ __redisSetError(c,REDIS_ERR_OTHER,"Invalid format string");
+ return REDIS_ERR;
+ }
+
+ if (__redisAppendCommand(c,cmd,len) != REDIS_OK) {
+ hi_free(cmd);
+ return REDIS_ERR;
+ }
+
+ hi_free(cmd);
+ return REDIS_OK;
+}
+
+int redisAppendCommand(redisContext *c, const char *format, ...) {
+ va_list ap;
+ int ret;
+
+ va_start(ap,format);
+ ret = redisvAppendCommand(c,format,ap);
+ va_end(ap);
+ return ret;
+}
+
+int redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen) {
+ hisds cmd;
+ long long len;
+
+ len = redisFormatSdsCommandArgv(&cmd,argc,argv,argvlen);
+ if (len == -1) {
+ __redisSetError(c,REDIS_ERR_OOM,"Out of memory");
+ return REDIS_ERR;
+ }
+
+ if (__redisAppendCommand(c,cmd,len) != REDIS_OK) {
+ hi_sdsfree(cmd);
+ return REDIS_ERR;
+ }
+
+ hi_sdsfree(cmd);
+ return REDIS_OK;
+}
+
+/* Helper function for the redisCommand* family of functions.
+ *
+ * Write a formatted command to the output buffer. If the given context is
+ * blocking, immediately read the reply into the "reply" pointer. When the
+ * context is non-blocking, the "reply" pointer will not be used and the
+ * command is simply appended to the write buffer.
+ *
+ * Returns the reply when a reply was successfully retrieved. Returns NULL
+ * otherwise. When NULL is returned in a blocking context, the error field
+ * in the context will be set.
+ */
+static void *__redisBlockForReply(redisContext *c) {
+ void *reply;
+
+ if (c->flags & REDIS_BLOCK) {
+ if (redisGetReply(c,&reply) != REDIS_OK)
+ return NULL;
+ return reply;
+ }
+ return NULL;
+}
+
+void *redisvCommand(redisContext *c, const char *format, va_list ap) {
+ if (redisvAppendCommand(c,format,ap) != REDIS_OK)
+ return NULL;
+ return __redisBlockForReply(c);
+}
+
+void *redisCommand(redisContext *c, const char *format, ...) {
+ va_list ap;
+ va_start(ap,format);
+ void *reply = redisvCommand(c,format,ap);
+ va_end(ap);
+ return reply;
+}
+
+void *redisCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen) {
+ if (redisAppendCommandArgv(c,argc,argv,argvlen) != REDIS_OK)
+ return NULL;
+ return __redisBlockForReply(c);
+}
diff --git a/deps/hiredis/hiredis.h b/deps/hiredis/hiredis.h
new file mode 100644
index 0000000..9c65901
--- /dev/null
+++ b/deps/hiredis/hiredis.h
@@ -0,0 +1,348 @@
+/*
+ * Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com>
+ * Copyright (c) 2010-2014, Pieter Noordhuis <pcnoordhuis at gmail dot com>
+ * Copyright (c) 2015, Matt Stancliff <matt at genges dot com>,
+ * Jan-Erik Rediger <janerik at fnordig dot com>
+ *
+ * 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.
+ */
+
+#ifndef __HIREDIS_H
+#define __HIREDIS_H
+#include "read.h"
+#include <stdarg.h> /* for va_list */
+#ifndef _MSC_VER
+#include <sys/time.h> /* for struct timeval */
+#else
+struct timeval; /* forward declaration */
+typedef long long ssize_t;
+#endif
+#include <stdint.h> /* uintXX_t, etc */
+#include "sds.h" /* for hisds */
+#include "alloc.h" /* for allocation wrappers */
+
+#define HIREDIS_MAJOR 1
+#define HIREDIS_MINOR 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. */
+#define REDIS_BLOCK 0x1
+
+/* Connection may be disconnected before being free'd. The second bit
+ * in the flags field is set when the context is connected. */
+#define REDIS_CONNECTED 0x2
+
+/* The async API might try to disconnect cleanly and flush the output
+ * buffer and read all subsequent replies before disconnecting.
+ * This flag means no new commands can come in and the connection
+ * should be terminated once all replies have been read. */
+#define REDIS_DISCONNECTING 0x4
+
+/* Flag specific to the async API which means that the context should be clean
+ * up as soon as possible. */
+#define REDIS_FREEING 0x8
+
+/* Flag that is set when an async callback is executed. */
+#define REDIS_IN_CALLBACK 0x10
+
+/* Flag that is set when the async context has one or more subscriptions. */
+#define REDIS_SUBSCRIBED 0x20
+
+/* Flag that is set when monitor mode is active */
+#define REDIS_MONITORING 0x40
+
+/* 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
+ * SO_REUSEADDR is being used. */
+#define REDIS_CONNECT_RETRIES 10
+
+/* Forward declarations for structs defined elsewhere */
+struct redisAsyncContext;
+struct redisContext;
+
+/* RESP3 push helpers and callback prototypes */
+#define redisIsPushReply(r) (((redisReply*)(r))->type == REDIS_REPLY_PUSH)
+typedef void (redisPushFn)(void *, void *);
+typedef void (redisAsyncPushFn)(struct redisAsyncContext *, void *);
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* This is the reply object returned by redisCommand() */
+typedef struct redisReply {
+ int type; /* REDIS_REPLY_* */
+ long long integer; /* The integer when type is REDIS_REPLY_INTEGER */
+ 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, 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 */
+ struct redisReply **element; /* elements vector for REDIS_REPLY_ARRAY */
+} redisReply;
+
+redisReader *redisReaderCreate(void);
+
+/* Function to free the reply objects hiredis returns by default. */
+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, ...);
+long long redisFormatCommandArgv(char **target, int argc, const char **argv, const size_t *argvlen);
+long long redisFormatSdsCommandArgv(hisds *target, int argc, const char ** argv, const size_t *argvlen);
+void redisFreeCommand(char *cmd);
+void redisFreeSdsCommand(hisds cmd);
+
+enum redisConnectionType {
+ REDIS_CONN_TCP,
+ REDIS_CONN_UNIX,
+ REDIS_CONN_USERFD
+};
+
+struct redisSsl;
+
+#define REDIS_OPT_NONBLOCK 0x01
+#define REDIS_OPT_REUSEADDR 0x02
+
+/**
+ * Don't automatically free the async object on a connection failure,
+ * or other implicit conditions. Only free on an explicit call to disconnect() or free()
+ */
+#define REDIS_OPT_NOAUTOFREE 0x04
+
+/* 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
+ * all bits set (~0) is INVALID_SOCKET. */
+#ifndef _WIN32
+typedef int redisFD;
+#define REDIS_INVALID_FD -1
+#else
+#ifdef _WIN64
+typedef unsigned long long redisFD; /* SOCKET = 64-bit UINT_PTR */
+#else
+typedef unsigned long redisFD; /* SOCKET = 32-bit UINT_PTR */
+#endif
+#define REDIS_INVALID_FD ((redisFD)(~0)) /* INVALID_SOCKET */
+#endif
+
+typedef struct {
+ /*
+ * the type of connection to use. This also indicates which
+ * `endpoint` member field to use
+ */
+ int type;
+ /* bit field of REDIS_OPT_xxx */
+ int options;
+ /* timeout value for connect operation. If NULL, no timeout is used */
+ const struct timeval *connect_timeout;
+ /* timeout value for commands. If NULL, no timeout is used. This can be
+ * updated at runtime with redisSetTimeout/redisAsyncSetTimeout. */
+ const struct timeval *command_timeout;
+ union {
+ /** use this field for tcp/ip connections */
+ struct {
+ const char *source_addr;
+ const char *ip;
+ int port;
+ } tcp;
+ /** use this field for unix domain sockets */
+ const char *unix_socket;
+ /**
+ * use this field to have hiredis operate an already-open
+ * file descriptor */
+ redisFD fd;
+ } endpoint;
+
+ /* Optional user defined data/destructor */
+ void *privdata;
+ void (*free_privdata)(void *);
+
+ /* A user defined PUSH message callback */
+ redisPushFn *push_cb;
+ redisAsyncPushFn *async_push_cb;
+} redisOptions;
+
+/**
+ * Helper macros to initialize options to their specified fields.
+ */
+#define REDIS_OPTIONS_SET_TCP(opts, ip_, port_) \
+ (opts)->type = REDIS_CONN_TCP; \
+ (opts)->endpoint.tcp.ip = ip_; \
+ (opts)->endpoint.tcp.port = port_;
+
+#define REDIS_OPTIONS_SET_UNIX(opts, path) \
+ (opts)->type = REDIS_CONN_UNIX; \
+ (opts)->endpoint.unix_socket = path;
+
+#define REDIS_OPTIONS_SET_PRIVDATA(opts, data, dtor) \
+ (opts)->privdata = data; \
+ (opts)->free_privdata = dtor; \
+
+typedef struct redisContextFuncs {
+ void (*free_privctx)(void *);
+ void (*async_read)(struct redisAsyncContext *);
+ void (*async_write)(struct redisAsyncContext *);
+ ssize_t (*read)(struct redisContext *, char *, size_t);
+ ssize_t (*write)(struct redisContext *);
+} redisContextFuncs;
+
+/* Context for a connection to Redis */
+typedef struct redisContext {
+ const redisContextFuncs *funcs; /* Function table */
+
+ int err; /* Error flags, 0 when there is no error */
+ char errstr[128]; /* String representation of error when applicable */
+ redisFD fd;
+ int flags;
+ char *obuf; /* Write buffer */
+ redisReader *reader; /* Protocol reader */
+
+ enum redisConnectionType connection_type;
+ struct timeval *connect_timeout;
+ struct timeval *command_timeout;
+
+ struct {
+ char *host;
+ char *source_addr;
+ int port;
+ } tcp;
+
+ struct {
+ char *path;
+ } unix_sock;
+
+ /* For non-blocking connect */
+ struct sockaddr *saddr;
+ size_t addrlen;
+
+ /* Optional data and corresponding destructor users can use to provide
+ * context to a given redisContext. Not used by hiredis. */
+ void *privdata;
+ void (*free_privdata)(void *);
+
+ /* Internal context pointer presently used by hiredis to manage
+ * SSL connections. */
+ void *privctx;
+
+ /* An optional RESP3 PUSH handler */
+ redisPushFn *push_cb;
+} redisContext;
+
+redisContext *redisConnectWithOptions(const redisOptions *options);
+redisContext *redisConnect(const char *ip, int port);
+redisContext *redisConnectWithTimeout(const char *ip, int port, const struct timeval tv);
+redisContext *redisConnectNonBlock(const char *ip, int port);
+redisContext *redisConnectBindNonBlock(const char *ip, int port,
+ const char *source_addr);
+redisContext *redisConnectBindNonBlockWithReuse(const char *ip, int port,
+ const char *source_addr);
+redisContext *redisConnectUnix(const char *path);
+redisContext *redisConnectUnixWithTimeout(const char *path, const struct timeval tv);
+redisContext *redisConnectUnixNonBlock(const char *path);
+redisContext *redisConnectFd(redisFD fd);
+
+/**
+ * Reconnect the given context using the saved information.
+ *
+ * This re-uses the exact same connect options as in the initial connection.
+ * host, ip (or path), timeout and bind address are reused,
+ * flags are used unmodified from the existing context.
+ *
+ * Returns REDIS_OK on successful connect or REDIS_ERR otherwise.
+ */
+int redisReconnect(redisContext *c);
+
+redisPushFn *redisSetPushCallback(redisContext *c, redisPushFn *fn);
+int redisSetTimeout(redisContext *c, const struct timeval tv);
+int redisEnableKeepAlive(redisContext *c);
+void redisFree(redisContext *c);
+redisFD redisFreeKeepFd(redisContext *c);
+int redisBufferRead(redisContext *c);
+int redisBufferWrite(redisContext *c, int *done);
+
+/* In a blocking context, this function first checks if there are unconsumed
+ * replies to return and returns one if so. Otherwise, it flushes the output
+ * buffer to the socket and reads until it has a reply. In a non-blocking
+ * context, it will return unconsumed replies until there are no more. */
+int redisGetReply(redisContext *c, void **reply);
+int redisGetReplyFromReader(redisContext *c, void **reply);
+
+/* Write a formatted command to the output buffer. Use these functions in blocking mode
+ * to get a pipeline of commands. */
+int redisAppendFormattedCommand(redisContext *c, const char *cmd, size_t len);
+
+/* Write a command to the output buffer. Use these functions in blocking mode
+ * to get a pipeline of commands. */
+int redisvAppendCommand(redisContext *c, const char *format, va_list ap);
+int redisAppendCommand(redisContext *c, const char *format, ...);
+int redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen);
+
+/* Issue a command to Redis. In a blocking context, it is identical to calling
+ * redisAppendCommand, followed by redisGetReply. The function will return
+ * NULL if there was an error in performing the request, otherwise it will
+ * return the reply. In a non-blocking context, it is identical to calling
+ * only redisAppendCommand and will always return NULL. */
+void *redisvCommand(redisContext *c, const char *format, va_list ap);
+void *redisCommand(redisContext *c, const char *format, ...);
+void *redisCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/deps/hiredis/hiredis.pc.in b/deps/hiredis/hiredis.pc.in
new file mode 100644
index 0000000..91b7731
--- /dev/null
+++ b/deps/hiredis/hiredis.pc.in
@@ -0,0 +1,12 @@
+prefix=@CMAKE_INSTALL_PREFIX@
+install_libdir=@CMAKE_INSTALL_LIBDIR@
+exec_prefix=${prefix}
+libdir=${exec_prefix}/${install_libdir}
+includedir=${prefix}/include
+pkgincludedir=${includedir}/hiredis
+
+Name: hiredis
+Description: Minimalistic C client library for Redis.
+Version: @PROJECT_VERSION@
+Libs: -L${libdir} -lhiredis
+Cflags: -I${pkgincludedir} -D_FILE_OFFSET_BITS=64
diff --git a/deps/hiredis/hiredis.targets b/deps/hiredis/hiredis.targets
new file mode 100644
index 0000000..effd8a5
--- /dev/null
+++ b/deps/hiredis/hiredis.targets
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemDefinitionGroup>
+ <ClCompile>
+ <AdditionalIncludeDirectories>$(MSBuildThisFileDirectory)\..\..\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ </ClCompile>
+ <Link>
+ <AdditionalLibraryDirectories>$(MSBuildThisFileDirectory)\..\..\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ </Link>
+ </ItemDefinitionGroup>
+</Project> \ No newline at end of file
diff --git a/deps/hiredis/hiredis_ssl-config.cmake.in b/deps/hiredis/hiredis_ssl-config.cmake.in
new file mode 100644
index 0000000..9a283df
--- /dev/null
+++ b/deps/hiredis/hiredis_ssl-config.cmake.in
@@ -0,0 +1,13 @@
+@PACKAGE_INIT@
+
+set_and_check(hiredis_ssl_INCLUDEDIR "@PACKAGE_INCLUDE_INSTALL_DIR@")
+
+IF (NOT TARGET hiredis::hiredis_ssl)
+ INCLUDE(${CMAKE_CURRENT_LIST_DIR}/hiredis_ssl-targets.cmake)
+ENDIF()
+
+SET(hiredis_ssl_LIBRARIES hiredis::hiredis_ssl)
+SET(hiredis_ssl_INCLUDE_DIRS ${hiredis_ssl_INCLUDEDIR})
+
+check_required_components(hiredis_ssl)
+
diff --git a/deps/hiredis/hiredis_ssl.h b/deps/hiredis/hiredis_ssl.h
new file mode 100644
index 0000000..50fb77c
--- /dev/null
+++ b/deps/hiredis/hiredis_ssl.h
@@ -0,0 +1,129 @@
+
+/*
+ * Copyright (c) 2019, Redis Labs
+ *
+ * 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.
+ */
+
+#ifndef __HIREDIS_SSL_H
+#define __HIREDIS_SSL_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* This is the underlying struct for SSL in ssl.h, which is not included to
+ * keep build dependencies short here.
+ */
+struct ssl_st;
+
+/* A wrapper around OpenSSL SSL_CTX to allow easy SSL use without directly
+ * calling OpenSSL.
+ */
+typedef struct redisSSLContext redisSSLContext;
+
+/**
+ * Initialization errors that redisCreateSSLContext() may return.
+ */
+
+typedef enum {
+ REDIS_SSL_CTX_NONE = 0, /* No Error */
+ REDIS_SSL_CTX_CREATE_FAILED, /* Failed to create OpenSSL SSL_CTX */
+ 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_OS_CERTSTORE_OPEN_FAILED, /* Failed to open system certificate store */
+ REDIS_SSL_CTX_OS_CERT_ADD_FAILED /* Failed to add CA certificates obtained from system to the SSL context */
+} redisSSLContextError;
+
+/**
+ * Return the error message corresponding with the specified error code.
+ */
+
+const char *redisSSLContextGetError(redisSSLContextError error);
+
+/**
+ * Helper function to initialize the OpenSSL library.
+ *
+ * OpenSSL requires one-time initialization before it can be used. Callers should
+ * call this function only once, and only if OpenSSL is not directly initialized
+ * elsewhere.
+ */
+int redisInitOpenSSL(void);
+
+/**
+ * Helper function to initialize an OpenSSL context that can be used
+ * to initiate SSL connections.
+ *
+ * cacert_filename is an optional name of a CA certificate/bundle file to load
+ * and use for validation.
+ *
+ * capath is an optional directory path where trusted CA certificate files are
+ * stored in an OpenSSL-compatible structure.
+ *
+ * cert_filename and private_key_filename are optional names of a client side
+ * certificate and private key files to use for authentication. They need to
+ * be both specified or omitted.
+ *
+ * server_name is an optional and will be used as a server name indication
+ * (SNI) TLS extension.
+ *
+ * If error is non-null, it will be populated in case the context creation fails
+ * (returning a NULL).
+ */
+
+redisSSLContext *redisCreateSSLContext(const char *cacert_filename, const char *capath,
+ const char *cert_filename, const char *private_key_filename,
+ const char *server_name, redisSSLContextError *error);
+
+/**
+ * Free a previously created OpenSSL context.
+ */
+void redisFreeSSLContext(redisSSLContext *redis_ssl_ctx);
+
+/**
+ * Initiate SSL on an existing redisContext.
+ *
+ * This is similar to redisInitiateSSL() but does not require the caller
+ * to directly interact with OpenSSL, and instead uses a redisSSLContext
+ * previously created using redisCreateSSLContext().
+ */
+
+int redisInitiateSSLWithContext(redisContext *c, redisSSLContext *redis_ssl_ctx);
+
+/**
+ * Initiate SSL/TLS negotiation on a provided OpenSSL SSL object.
+ */
+
+int redisInitiateSSL(redisContext *c, struct ssl_st *ssl);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __HIREDIS_SSL_H */
diff --git a/deps/hiredis/hiredis_ssl.pc.in b/deps/hiredis/hiredis_ssl.pc.in
new file mode 100644
index 0000000..588a978
--- /dev/null
+++ b/deps/hiredis/hiredis_ssl.pc.in
@@ -0,0 +1,12 @@
+prefix=@CMAKE_INSTALL_PREFIX@
+exec_prefix=${prefix}
+libdir=${exec_prefix}/lib
+includedir=${prefix}/include
+pkgincludedir=${includedir}/hiredis
+
+Name: hiredis_ssl
+Description: SSL Support for hiredis.
+Version: @PROJECT_VERSION@
+Requires: hiredis
+Libs: -L${libdir} -lhiredis_ssl
+Libs.private: -lssl -lcrypto
diff --git a/deps/hiredis/net.c b/deps/hiredis/net.c
new file mode 100644
index 0000000..88f9aff
--- /dev/null
+++ b/deps/hiredis/net.c
@@ -0,0 +1,612 @@
+/* Extracted from anet.c to work properly with Hiredis error reporting.
+ *
+ * Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com>
+ * Copyright (c) 2010-2014, Pieter Noordhuis <pcnoordhuis at gmail dot com>
+ * Copyright (c) 2015, Matt Stancliff <matt at genges dot com>,
+ * Jan-Erik Rediger <janerik at fnordig dot com>
+ *
+ * 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 "fmacros.h"
+#include <sys/types.h>
+#include <fcntl.h>
+#include <string.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <limits.h>
+#include <stdlib.h>
+
+#include "net.h"
+#include "sds.h"
+#include "sockcompat.h"
+#include "win32.h"
+
+/* Defined in hiredis.c */
+void __redisSetError(redisContext *c, int type, const char *str);
+
+void redisNetClose(redisContext *c) {
+ if (c && c->fd != REDIS_INVALID_FD) {
+ close(c->fd);
+ c->fd = REDIS_INVALID_FD;
+ }
+}
+
+ssize_t redisNetRead(redisContext *c, char *buf, size_t bufcap) {
+ ssize_t nread = recv(c->fd, buf, bufcap, 0);
+ if (nread == -1) {
+ if ((errno == EWOULDBLOCK && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) {
+ /* Try again later */
+ return 0;
+ } else if(errno == ETIMEDOUT && (c->flags & REDIS_BLOCK)) {
+ /* especially in windows */
+ __redisSetError(c, REDIS_ERR_TIMEOUT, "recv timeout");
+ return -1;
+ } else {
+ __redisSetError(c, REDIS_ERR_IO, NULL);
+ return -1;
+ }
+ } else if (nread == 0) {
+ __redisSetError(c, REDIS_ERR_EOF, "Server closed the connection");
+ return -1;
+ } else {
+ return nread;
+ }
+}
+
+ssize_t redisNetWrite(redisContext *c) {
+ ssize_t nwritten = send(c->fd, c->obuf, hi_sdslen(c->obuf), 0);
+ if (nwritten < 0) {
+ if ((errno == EWOULDBLOCK && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) {
+ /* Try again later */
+ } else {
+ __redisSetError(c, REDIS_ERR_IO, NULL);
+ return -1;
+ }
+ }
+ return nwritten;
+}
+
+static void __redisSetErrorFromErrno(redisContext *c, int type, const char *prefix) {
+ int errorno = errno; /* snprintf() may change errno */
+ char buf[128] = { 0 };
+ size_t len = 0;
+
+ if (prefix != NULL)
+ len = snprintf(buf,sizeof(buf),"%s: ",prefix);
+ strerror_r(errorno, (char *)(buf + len), sizeof(buf) - len);
+ __redisSetError(c,type,buf);
+}
+
+static int redisSetReuseAddr(redisContext *c) {
+ int on = 1;
+ if (setsockopt(c->fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) {
+ __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
+ redisNetClose(c);
+ return REDIS_ERR;
+ }
+ return REDIS_OK;
+}
+
+static int redisCreateSocket(redisContext *c, int type) {
+ redisFD s;
+ if ((s = socket(type, SOCK_STREAM, 0)) == REDIS_INVALID_FD) {
+ __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
+ return REDIS_ERR;
+ }
+ c->fd = s;
+ if (type == AF_INET) {
+ if (redisSetReuseAddr(c) == REDIS_ERR) {
+ return REDIS_ERR;
+ }
+ }
+ return REDIS_OK;
+}
+
+static int redisSetBlocking(redisContext *c, int blocking) {
+#ifndef _WIN32
+ int flags;
+
+ /* Set the socket nonblocking.
+ * Note that fcntl(2) for F_GETFL and F_SETFL can't be
+ * interrupted by a signal. */
+ if ((flags = fcntl(c->fd, F_GETFL)) == -1) {
+ __redisSetErrorFromErrno(c,REDIS_ERR_IO,"fcntl(F_GETFL)");
+ redisNetClose(c);
+ return REDIS_ERR;
+ }
+
+ if (blocking)
+ flags &= ~O_NONBLOCK;
+ else
+ flags |= O_NONBLOCK;
+
+ if (fcntl(c->fd, F_SETFL, flags) == -1) {
+ __redisSetErrorFromErrno(c,REDIS_ERR_IO,"fcntl(F_SETFL)");
+ redisNetClose(c);
+ return REDIS_ERR;
+ }
+#else
+ u_long mode = blocking ? 0 : 1;
+ if (ioctl(c->fd, FIONBIO, &mode) == -1) {
+ __redisSetErrorFromErrno(c, REDIS_ERR_IO, "ioctl(FIONBIO)");
+ redisNetClose(c);
+ return REDIS_ERR;
+ }
+#endif /* _WIN32 */
+ return REDIS_OK;
+}
+
+int redisKeepAlive(redisContext *c, int interval) {
+ int val = 1;
+ redisFD fd = c->fd;
+
+ if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &val, sizeof(val)) == -1){
+ __redisSetError(c,REDIS_ERR_OTHER,strerror(errno));
+ return REDIS_ERR;
+ }
+
+ val = interval;
+
+#if defined(__APPLE__) && defined(__MACH__)
+ if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPALIVE, &val, sizeof(val)) < 0) {
+ __redisSetError(c,REDIS_ERR_OTHER,strerror(errno));
+ return REDIS_ERR;
+ }
+#else
+#if defined(__GLIBC__) && !defined(__FreeBSD_kernel__)
+ if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE, &val, sizeof(val)) < 0) {
+ __redisSetError(c,REDIS_ERR_OTHER,strerror(errno));
+ return REDIS_ERR;
+ }
+
+ val = interval/3;
+ if (val == 0) val = 1;
+ if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPINTVL, &val, sizeof(val)) < 0) {
+ __redisSetError(c,REDIS_ERR_OTHER,strerror(errno));
+ return REDIS_ERR;
+ }
+
+ val = 3;
+ if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPCNT, &val, sizeof(val)) < 0) {
+ __redisSetError(c,REDIS_ERR_OTHER,strerror(errno));
+ return REDIS_ERR;
+ }
+#endif
+#endif
+
+ return REDIS_OK;
+}
+
+int redisSetTcpNoDelay(redisContext *c) {
+ int yes = 1;
+ if (setsockopt(c->fd, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof(yes)) == -1) {
+ __redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(TCP_NODELAY)");
+ redisNetClose(c);
+ return REDIS_ERR;
+ }
+ return REDIS_OK;
+}
+
+#define __MAX_MSEC (((LONG_MAX) - 999) / 1000)
+
+static int redisContextTimeoutMsec(redisContext *c, long *result)
+{
+ const struct timeval *timeout = c->connect_timeout;
+ long msec = -1;
+
+ /* Only use timeout when not NULL. */
+ if (timeout != NULL) {
+ if (timeout->tv_usec > 1000000 || timeout->tv_sec > __MAX_MSEC) {
+ *result = msec;
+ return REDIS_ERR;
+ }
+
+ msec = (timeout->tv_sec * 1000) + ((timeout->tv_usec + 999) / 1000);
+
+ if (msec < 0 || msec > INT_MAX) {
+ msec = INT_MAX;
+ }
+ }
+
+ *result = msec;
+ return REDIS_OK;
+}
+
+static int redisContextWaitReady(redisContext *c, long msec) {
+ struct pollfd wfd[1];
+
+ wfd[0].fd = c->fd;
+ wfd[0].events = POLLOUT;
+
+ if (errno == EINPROGRESS) {
+ int res;
+
+ if ((res = poll(wfd, 1, msec)) == -1) {
+ __redisSetErrorFromErrno(c, REDIS_ERR_IO, "poll(2)");
+ redisNetClose(c);
+ return REDIS_ERR;
+ } else if (res == 0) {
+ errno = ETIMEDOUT;
+ __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
+ redisNetClose(c);
+ return REDIS_ERR;
+ }
+
+ if (redisCheckConnectDone(c, &res) != REDIS_OK || res == 0) {
+ redisCheckSocketError(c);
+ return REDIS_ERR;
+ }
+
+ return REDIS_OK;
+ }
+
+ __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
+ redisNetClose(c);
+ return REDIS_ERR;
+}
+
+int redisCheckConnectDone(redisContext *c, int *completed) {
+ int rc = connect(c->fd, (const struct sockaddr *)c->saddr, c->addrlen);
+ if (rc == 0) {
+ *completed = 1;
+ return REDIS_OK;
+ }
+ switch (errno) {
+ case EISCONN:
+ *completed = 1;
+ return REDIS_OK;
+ case EALREADY:
+ case EINPROGRESS:
+ case EWOULDBLOCK:
+ *completed = 0;
+ return REDIS_OK;
+ default:
+ return REDIS_ERR;
+ }
+}
+
+int redisCheckSocketError(redisContext *c) {
+ int err = 0, errno_saved = errno;
+ socklen_t errlen = sizeof(err);
+
+ if (getsockopt(c->fd, SOL_SOCKET, SO_ERROR, &err, &errlen) == -1) {
+ __redisSetErrorFromErrno(c,REDIS_ERR_IO,"getsockopt(SO_ERROR)");
+ return REDIS_ERR;
+ }
+
+ if (err == 0) {
+ err = errno_saved;
+ }
+
+ if (err) {
+ errno = err;
+ __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
+ return REDIS_ERR;
+ }
+
+ return REDIS_OK;
+}
+
+int redisContextSetTimeout(redisContext *c, const struct timeval tv) {
+ const void *to_ptr = &tv;
+ size_t to_sz = sizeof(tv);
+
+ if (setsockopt(c->fd,SOL_SOCKET,SO_RCVTIMEO,to_ptr,to_sz) == -1) {
+ __redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(SO_RCVTIMEO)");
+ return REDIS_ERR;
+ }
+ if (setsockopt(c->fd,SOL_SOCKET,SO_SNDTIMEO,to_ptr,to_sz) == -1) {
+ __redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(SO_SNDTIMEO)");
+ return REDIS_ERR;
+ }
+ return REDIS_OK;
+}
+
+int redisContextUpdateConnectTimeout(redisContext *c, const struct timeval *timeout) {
+ /* Same timeval struct, short circuit */
+ if (c->connect_timeout == timeout)
+ return REDIS_OK;
+
+ /* Allocate context timeval if we need to */
+ if (c->connect_timeout == NULL) {
+ c->connect_timeout = hi_malloc(sizeof(*c->connect_timeout));
+ if (c->connect_timeout == NULL)
+ return REDIS_ERR;
+ }
+
+ memcpy(c->connect_timeout, timeout, sizeof(*c->connect_timeout));
+ return REDIS_OK;
+}
+
+int redisContextUpdateCommandTimeout(redisContext *c, const struct timeval *timeout) {
+ /* Same timeval struct, short circuit */
+ if (c->command_timeout == timeout)
+ return REDIS_OK;
+
+ /* Allocate context timeval if we need to */
+ if (c->command_timeout == NULL) {
+ c->command_timeout = hi_malloc(sizeof(*c->command_timeout));
+ if (c->command_timeout == NULL)
+ return REDIS_ERR;
+ }
+
+ memcpy(c->command_timeout, timeout, sizeof(*c->command_timeout));
+ return REDIS_OK;
+}
+
+static int _redisContextConnectTcp(redisContext *c, const char *addr, int port,
+ const struct timeval *timeout,
+ const char *source_addr) {
+ redisFD s;
+ int rv, n;
+ char _port[6]; /* strlen("65535"); */
+ struct addrinfo hints, *servinfo, *bservinfo, *p, *b;
+ int blocking = (c->flags & REDIS_BLOCK);
+ int reuseaddr = (c->flags & REDIS_REUSEADDR);
+ int reuses = 0;
+ long timeout_msec = -1;
+
+ servinfo = NULL;
+ c->connection_type = REDIS_CONN_TCP;
+ c->tcp.port = port;
+
+ /* We need to take possession of the passed parameters
+ * to make them reusable for a reconnect.
+ * We also carefully check we don't free data we already own,
+ * as in the case of the reconnect method.
+ *
+ * This is a bit ugly, but atleast it works and doesn't leak memory.
+ **/
+ if (c->tcp.host != addr) {
+ hi_free(c->tcp.host);
+
+ c->tcp.host = hi_strdup(addr);
+ if (c->tcp.host == NULL)
+ goto oom;
+ }
+
+ if (timeout) {
+ if (redisContextUpdateConnectTimeout(c, timeout) == REDIS_ERR)
+ goto oom;
+ } else {
+ hi_free(c->connect_timeout);
+ c->connect_timeout = NULL;
+ }
+
+ if (redisContextTimeoutMsec(c, &timeout_msec) != REDIS_OK) {
+ __redisSetError(c, REDIS_ERR_IO, "Invalid timeout specified");
+ goto error;
+ }
+
+ if (source_addr == NULL) {
+ hi_free(c->tcp.source_addr);
+ c->tcp.source_addr = NULL;
+ } else if (c->tcp.source_addr != source_addr) {
+ hi_free(c->tcp.source_addr);
+ c->tcp.source_addr = hi_strdup(source_addr);
+ }
+
+ snprintf(_port, 6, "%d", port);
+ memset(&hints,0,sizeof(hints));
+ hints.ai_family = AF_INET;
+ hints.ai_socktype = SOCK_STREAM;
+
+ /* Try with IPv6 if no IPv4 address was found. We do it in this order since
+ * in a Redis client you can't afford to test if you have IPv6 connectivity
+ * as this would add latency to every connect. Otherwise a more sensible
+ * route could be: Use IPv6 if both addresses are available and there is IPv6
+ * connectivity. */
+ if ((rv = getaddrinfo(c->tcp.host,_port,&hints,&servinfo)) != 0) {
+ hints.ai_family = AF_INET6;
+ if ((rv = getaddrinfo(addr,_port,&hints,&servinfo)) != 0) {
+ __redisSetError(c,REDIS_ERR_OTHER,gai_strerror(rv));
+ return REDIS_ERR;
+ }
+ }
+ for (p = servinfo; p != NULL; p = p->ai_next) {
+addrretry:
+ if ((s = socket(p->ai_family,p->ai_socktype,p->ai_protocol)) == REDIS_INVALID_FD)
+ continue;
+
+ c->fd = s;
+ if (redisSetBlocking(c,0) != REDIS_OK)
+ goto error;
+ if (c->tcp.source_addr) {
+ int bound = 0;
+ /* Using getaddrinfo saves us from self-determining IPv4 vs IPv6 */
+ if ((rv = getaddrinfo(c->tcp.source_addr, NULL, &hints, &bservinfo)) != 0) {
+ char buf[128];
+ snprintf(buf,sizeof(buf),"Can't get addr: %s",gai_strerror(rv));
+ __redisSetError(c,REDIS_ERR_OTHER,buf);
+ goto error;
+ }
+
+ if (reuseaddr) {
+ n = 1;
+ if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char*) &n,
+ sizeof(n)) < 0) {
+ freeaddrinfo(bservinfo);
+ goto error;
+ }
+ }
+
+ for (b = bservinfo; b != NULL; b = b->ai_next) {
+ if (bind(s,b->ai_addr,b->ai_addrlen) != -1) {
+ bound = 1;
+ break;
+ }
+ }
+ freeaddrinfo(bservinfo);
+ if (!bound) {
+ char buf[128];
+ snprintf(buf,sizeof(buf),"Can't bind socket: %s",strerror(errno));
+ __redisSetError(c,REDIS_ERR_OTHER,buf);
+ goto error;
+ }
+ }
+
+ /* For repeat connection */
+ hi_free(c->saddr);
+ c->saddr = hi_malloc(p->ai_addrlen);
+ if (c->saddr == NULL)
+ goto oom;
+
+ memcpy(c->saddr, p->ai_addr, p->ai_addrlen);
+ c->addrlen = p->ai_addrlen;
+
+ if (connect(s,p->ai_addr,p->ai_addrlen) == -1) {
+ if (errno == EHOSTUNREACH) {
+ redisNetClose(c);
+ continue;
+ } else if (errno == EINPROGRESS) {
+ if (blocking) {
+ goto wait_for_ready;
+ }
+ /* This is ok.
+ * Note that even when it's in blocking mode, we unset blocking
+ * for `connect()`
+ */
+ } else if (errno == EADDRNOTAVAIL && reuseaddr) {
+ if (++reuses >= REDIS_CONNECT_RETRIES) {
+ goto error;
+ } else {
+ redisNetClose(c);
+ goto addrretry;
+ }
+ } else {
+ wait_for_ready:
+ if (redisContextWaitReady(c,timeout_msec) != REDIS_OK)
+ goto error;
+ if (redisSetTcpNoDelay(c) != REDIS_OK)
+ goto error;
+ }
+ }
+ if (blocking && redisSetBlocking(c,1) != REDIS_OK)
+ goto error;
+
+ c->flags |= REDIS_CONNECTED;
+ rv = REDIS_OK;
+ goto end;
+ }
+ if (p == NULL) {
+ char buf[128];
+ snprintf(buf,sizeof(buf),"Can't create socket: %s",strerror(errno));
+ __redisSetError(c,REDIS_ERR_OTHER,buf);
+ goto error;
+ }
+
+oom:
+ __redisSetError(c, REDIS_ERR_OOM, "Out of memory");
+error:
+ rv = REDIS_ERR;
+end:
+ if(servinfo) {
+ freeaddrinfo(servinfo);
+ }
+
+ return rv; // Need to return REDIS_OK if alright
+}
+
+int redisContextConnectTcp(redisContext *c, const char *addr, int port,
+ const struct timeval *timeout) {
+ return _redisContextConnectTcp(c, addr, port, timeout, NULL);
+}
+
+int redisContextConnectBindTcp(redisContext *c, const char *addr, int port,
+ const struct timeval *timeout,
+ const char *source_addr) {
+ return _redisContextConnectTcp(c, addr, port, timeout, source_addr);
+}
+
+int redisContextConnectUnix(redisContext *c, const char *path, const struct timeval *timeout) {
+#ifndef _WIN32
+ int blocking = (c->flags & REDIS_BLOCK);
+ struct sockaddr_un *sa;
+ long timeout_msec = -1;
+
+ if (redisCreateSocket(c,AF_UNIX) < 0)
+ return REDIS_ERR;
+ if (redisSetBlocking(c,0) != REDIS_OK)
+ return REDIS_ERR;
+
+ c->connection_type = REDIS_CONN_UNIX;
+ if (c->unix_sock.path != path) {
+ hi_free(c->unix_sock.path);
+
+ c->unix_sock.path = hi_strdup(path);
+ if (c->unix_sock.path == NULL)
+ goto oom;
+ }
+
+ if (timeout) {
+ if (redisContextUpdateConnectTimeout(c, timeout) == REDIS_ERR)
+ goto oom;
+ } else {
+ hi_free(c->connect_timeout);
+ c->connect_timeout = NULL;
+ }
+
+ if (redisContextTimeoutMsec(c,&timeout_msec) != REDIS_OK)
+ return REDIS_ERR;
+
+ /* Don't leak sockaddr if we're reconnecting */
+ if (c->saddr) hi_free(c->saddr);
+
+ sa = (struct sockaddr_un*)(c->saddr = hi_malloc(sizeof(struct sockaddr_un)));
+ if (sa == NULL)
+ goto oom;
+
+ c->addrlen = sizeof(struct sockaddr_un);
+ sa->sun_family = AF_UNIX;
+ strncpy(sa->sun_path, path, sizeof(sa->sun_path) - 1);
+ if (connect(c->fd, (struct sockaddr*)sa, sizeof(*sa)) == -1) {
+ if (errno == EINPROGRESS && !blocking) {
+ /* This is ok. */
+ } else {
+ if (redisContextWaitReady(c,timeout_msec) != REDIS_OK)
+ return REDIS_ERR;
+ }
+ }
+
+ /* Reset socket to be blocking after connect(2). */
+ if (blocking && redisSetBlocking(c,1) != REDIS_OK)
+ return REDIS_ERR;
+
+ c->flags |= REDIS_CONNECTED;
+ return REDIS_OK;
+#else
+ /* We currently do not support Unix sockets for Windows. */
+ /* TODO(m): https://devblogs.microsoft.com/commandline/af_unix-comes-to-windows/ */
+ errno = EPROTONOSUPPORT;
+ return REDIS_ERR;
+#endif /* _WIN32 */
+oom:
+ __redisSetError(c, REDIS_ERR_OOM, "Out of memory");
+ return REDIS_ERR;
+}
diff --git a/deps/hiredis/net.h b/deps/hiredis/net.h
new file mode 100644
index 0000000..9f43283
--- /dev/null
+++ b/deps/hiredis/net.h
@@ -0,0 +1,56 @@
+/* Extracted from anet.c to work properly with Hiredis error reporting.
+ *
+ * Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com>
+ * Copyright (c) 2010-2014, Pieter Noordhuis <pcnoordhuis at gmail dot com>
+ * Copyright (c) 2015, Matt Stancliff <matt at genges dot com>,
+ * Jan-Erik Rediger <janerik at fnordig dot com>
+ *
+ * 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.
+ */
+
+#ifndef __NET_H
+#define __NET_H
+
+#include "hiredis.h"
+
+void redisNetClose(redisContext *c);
+ssize_t redisNetRead(redisContext *c, char *buf, size_t bufcap);
+ssize_t redisNetWrite(redisContext *c);
+
+int redisCheckSocketError(redisContext *c);
+int redisContextSetTimeout(redisContext *c, const struct timeval tv);
+int redisContextConnectTcp(redisContext *c, const char *addr, int port, const struct timeval *timeout);
+int redisContextConnectBindTcp(redisContext *c, const char *addr, int port,
+ const struct timeval *timeout,
+ const char *source_addr);
+int redisContextConnectUnix(redisContext *c, const char *path, const struct timeval *timeout);
+int redisKeepAlive(redisContext *c, int interval);
+int redisCheckConnectDone(redisContext *c, int *completed);
+
+int redisSetTcpNoDelay(redisContext *c);
+
+#endif
diff --git a/deps/hiredis/read.c b/deps/hiredis/read.c
new file mode 100644
index 0000000..6c19c5a
--- /dev/null
+++ b/deps/hiredis/read.c
@@ -0,0 +1,785 @@
+/*
+ * Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com>
+ * Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com>
+ *
+ * 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 "fmacros.h"
+#include <string.h>
+#include <stdlib.h>
+#ifndef _MSC_VER
+#include <unistd.h>
+#include <strings.h>
+#endif
+#include <assert.h>
+#include <errno.h>
+#include <ctype.h>
+#include <limits.h>
+#include <math.h>
+
+#include "alloc.h"
+#include "read.h"
+#include "sds.h"
+#include "win32.h"
+
+/* Initial size of our nested reply stack and how much we grow it when needd */
+#define REDIS_READER_STACK_SIZE 9
+
+static void __redisReaderSetError(redisReader *r, int type, const char *str) {
+ size_t len;
+
+ if (r->reply != NULL && r->fn && r->fn->freeObject) {
+ r->fn->freeObject(r->reply);
+ r->reply = NULL;
+ }
+
+ /* Clear input buffer on errors. */
+ hi_sdsfree(r->buf);
+ r->buf = NULL;
+ r->pos = r->len = 0;
+
+ /* Reset task stack. */
+ r->ridx = -1;
+
+ /* Set error. */
+ r->err = type;
+ len = strlen(str);
+ len = len < (sizeof(r->errstr)-1) ? len : (sizeof(r->errstr)-1);
+ memcpy(r->errstr,str,len);
+ r->errstr[len] = '\0';
+}
+
+static size_t chrtos(char *buf, size_t size, char byte) {
+ size_t len = 0;
+
+ switch(byte) {
+ case '\\':
+ case '"':
+ len = snprintf(buf,size,"\"\\%c\"",byte);
+ break;
+ case '\n': len = snprintf(buf,size,"\"\\n\""); break;
+ case '\r': len = snprintf(buf,size,"\"\\r\""); break;
+ case '\t': len = snprintf(buf,size,"\"\\t\""); break;
+ case '\a': len = snprintf(buf,size,"\"\\a\""); break;
+ case '\b': len = snprintf(buf,size,"\"\\b\""); break;
+ default:
+ if (isprint(byte))
+ len = snprintf(buf,size,"\"%c\"",byte);
+ else
+ len = snprintf(buf,size,"\"\\x%02x\"",(unsigned char)byte);
+ break;
+ }
+
+ return len;
+}
+
+static void __redisReaderSetErrorProtocolByte(redisReader *r, char byte) {
+ char cbuf[8], sbuf[128];
+
+ chrtos(cbuf,sizeof(cbuf),byte);
+ snprintf(sbuf,sizeof(sbuf),
+ "Protocol error, got %s as reply type byte", cbuf);
+ __redisReaderSetError(r,REDIS_ERR_PROTOCOL,sbuf);
+}
+
+static void __redisReaderSetErrorOOM(redisReader *r) {
+ __redisReaderSetError(r,REDIS_ERR_OOM,"Out of memory");
+}
+
+static char *readBytes(redisReader *r, unsigned int bytes) {
+ char *p;
+ if (r->len-r->pos >= bytes) {
+ p = r->buf+r->pos;
+ r->pos += bytes;
+ return p;
+ }
+ return NULL;
+}
+
+/* Find pointer to \r\n. */
+static char *seekNewline(char *s, size_t len) {
+ char *ret;
+
+ /* We cannot match with fewer than 2 bytes */
+ if (len < 2)
+ return NULL;
+
+ /* Search up to len - 1 characters */
+ len--;
+
+ /* Look for the \r */
+ while ((ret = memchr(s, '\r', len)) != NULL) {
+ if (ret[1] == '\n') {
+ /* Found. */
+ break;
+ }
+ /* Continue searching. */
+ ret++;
+ len -= ret - s;
+ s = ret;
+ }
+
+ return ret;
+}
+
+/* Convert a string into a long long. Returns REDIS_OK if the string could be
+ * parsed into a (non-overflowing) long long, REDIS_ERR otherwise. The value
+ * will be set to the parsed value when appropriate.
+ *
+ * Note that this function demands that the string strictly represents
+ * a long long: no spaces or other characters before or after the string
+ * representing the number are accepted, nor zeroes at the start if not
+ * for the string "0" representing the zero number.
+ *
+ * Because of its strictness, it is safe to use this function to check if
+ * you can convert a string into a long long, and obtain back the string
+ * from the number without any loss in the string representation. */
+static int string2ll(const char *s, size_t slen, long long *value) {
+ const char *p = s;
+ size_t plen = 0;
+ int negative = 0;
+ unsigned long long v;
+
+ if (plen == slen)
+ return REDIS_ERR;
+
+ /* Special case: first and only digit is 0. */
+ if (slen == 1 && p[0] == '0') {
+ if (value != NULL) *value = 0;
+ return REDIS_OK;
+ }
+
+ if (p[0] == '-') {
+ negative = 1;
+ p++; plen++;
+
+ /* Abort on only a negative sign. */
+ if (plen == slen)
+ return REDIS_ERR;
+ }
+
+ /* First digit should be 1-9, otherwise the string should just be 0. */
+ if (p[0] >= '1' && p[0] <= '9') {
+ v = p[0]-'0';
+ p++; plen++;
+ } else if (p[0] == '0' && slen == 1) {
+ *value = 0;
+ return REDIS_OK;
+ } else {
+ return REDIS_ERR;
+ }
+
+ while (plen < slen && p[0] >= '0' && p[0] <= '9') {
+ if (v > (ULLONG_MAX / 10)) /* Overflow. */
+ return REDIS_ERR;
+ v *= 10;
+
+ if (v > (ULLONG_MAX - (p[0]-'0'))) /* Overflow. */
+ return REDIS_ERR;
+ v += p[0]-'0';
+
+ p++; plen++;
+ }
+
+ /* Return if not all bytes were used. */
+ if (plen < slen)
+ return REDIS_ERR;
+
+ if (negative) {
+ if (v > ((unsigned long long)(-(LLONG_MIN+1))+1)) /* Overflow. */
+ return REDIS_ERR;
+ if (value != NULL) *value = -v;
+ } else {
+ if (v > LLONG_MAX) /* Overflow. */
+ return REDIS_ERR;
+ if (value != NULL) *value = v;
+ }
+ return REDIS_OK;
+}
+
+static char *readLine(redisReader *r, int *_len) {
+ char *p, *s;
+ int len;
+
+ p = r->buf+r->pos;
+ s = seekNewline(p,(r->len-r->pos));
+ if (s != NULL) {
+ len = s-(r->buf+r->pos);
+ r->pos += len+2; /* skip \r\n */
+ if (_len) *_len = len;
+ return p;
+ }
+ return NULL;
+}
+
+static void moveToNextTask(redisReader *r) {
+ redisReadTask *cur, *prv;
+ while (r->ridx >= 0) {
+ /* Return a.s.a.p. when the stack is now empty. */
+ if (r->ridx == 0) {
+ r->ridx--;
+ return;
+ }
+
+ cur = r->task[r->ridx];
+ prv = r->task[r->ridx-1];
+ assert(prv->type == REDIS_REPLY_ARRAY ||
+ prv->type == REDIS_REPLY_MAP ||
+ prv->type == REDIS_REPLY_SET ||
+ prv->type == REDIS_REPLY_PUSH);
+ if (cur->idx == prv->elements-1) {
+ r->ridx--;
+ } else {
+ /* Reset the type because the next item can be anything */
+ assert(cur->idx < prv->elements);
+ cur->type = -1;
+ cur->elements = -1;
+ cur->idx++;
+ return;
+ }
+ }
+}
+
+static int processLineItem(redisReader *r) {
+ redisReadTask *cur = r->task[r->ridx];
+ void *obj;
+ char *p;
+ int len;
+
+ 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) {
+ obj = r->fn->createInteger(cur,v);
+ } else {
+ obj = (void*)REDIS_REPLY_INTEGER;
+ }
+ } else if (cur->type == REDIS_REPLY_DOUBLE) {
+ char buf[326], *eptr;
+ double d;
+
+ 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,
+ "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;
+
+ 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
+ obj = (void*)(size_t)(cur->type);
+ }
+
+ if (obj == NULL) {
+ __redisReaderSetErrorOOM(r);
+ return REDIS_ERR;
+ }
+
+ /* Set reply if this is the root object. */
+ if (r->ridx == 0) r->reply = obj;
+ moveToNextTask(r);
+ return REDIS_OK;
+ }
+
+ return REDIS_ERR;
+}
+
+static int processBulkItem(redisReader *r) {
+ redisReadTask *cur = r->task[r->ridx];
+ void *obj = NULL;
+ char *p, *s;
+ long long len;
+ unsigned long bytelen;
+ int success = 0;
+
+ p = r->buf+r->pos;
+ s = seekNewline(p,r->len-r->pos);
+ if (s != NULL) {
+ p = r->buf+r->pos;
+ bytelen = s-(r->buf+r->pos)+2; /* include \r\n */
+
+ if (string2ll(p, bytelen - 2, &len) == REDIS_ERR) {
+ __redisReaderSetError(r,REDIS_ERR_PROTOCOL,
+ "Bad bulk string length");
+ return REDIS_ERR;
+ }
+
+ if (len < -1 || (LLONG_MAX > SIZE_MAX && len > (long long)SIZE_MAX)) {
+ __redisReaderSetError(r,REDIS_ERR_PROTOCOL,
+ "Bulk string length out of range");
+ return REDIS_ERR;
+ }
+
+ if (len == -1) {
+ /* The nil object can always be created. */
+ if (r->fn && r->fn->createNil)
+ obj = r->fn->createNil(cur);
+ else
+ obj = (void*)REDIS_REPLY_NIL;
+ success = 1;
+ } else {
+ /* Only continue when the buffer contains the entire bulk item. */
+ bytelen += len+2; /* include \r\n */
+ if (r->pos+bytelen <= r->len) {
+ if ((cur->type == REDIS_REPLY_VERB && len < 4) ||
+ (cur->type == REDIS_REPLY_VERB && s[5] != ':'))
+ {
+ __redisReaderSetError(r,REDIS_ERR_PROTOCOL,
+ "Verbatim string 4 bytes of content type are "
+ "missing or incorrectly encoded.");
+ return REDIS_ERR;
+ }
+ if (r->fn && r->fn->createString)
+ obj = r->fn->createString(cur,s+2,len);
+ else
+ obj = (void*)(long)cur->type;
+ success = 1;
+ }
+ }
+
+ /* Proceed when obj was created. */
+ if (success) {
+ if (obj == NULL) {
+ __redisReaderSetErrorOOM(r);
+ return REDIS_ERR;
+ }
+
+ r->pos += bytelen;
+
+ /* Set reply if this is the root object. */
+ if (r->ridx == 0) r->reply = obj;
+ moveToNextTask(r);
+ return REDIS_OK;
+ }
+ }
+
+ return REDIS_ERR;
+}
+
+static int redisReaderGrow(redisReader *r) {
+ redisReadTask **aux;
+ int newlen;
+
+ /* Grow our stack size */
+ newlen = r->tasks + REDIS_READER_STACK_SIZE;
+ aux = hi_realloc(r->task, sizeof(*r->task) * newlen);
+ if (aux == NULL)
+ goto oom;
+
+ r->task = aux;
+
+ /* Allocate new tasks */
+ for (; r->tasks < newlen; r->tasks++) {
+ r->task[r->tasks] = hi_calloc(1, sizeof(**r->task));
+ if (r->task[r->tasks] == NULL)
+ goto oom;
+ }
+
+ return REDIS_OK;
+oom:
+ __redisReaderSetErrorOOM(r);
+ return REDIS_ERR;
+}
+
+/* Process the array, map and set types. */
+static int processAggregateItem(redisReader *r) {
+ redisReadTask *cur = r->task[r->ridx];
+ void *obj;
+ char *p;
+ long long elements;
+ int root = 0, len;
+
+ if (r->ridx == r->tasks - 1) {
+ if (redisReaderGrow(r) == REDIS_ERR)
+ return REDIS_ERR;
+ }
+
+ if ((p = readLine(r,&len)) != NULL) {
+ if (string2ll(p, len, &elements) == REDIS_ERR) {
+ __redisReaderSetError(r,REDIS_ERR_PROTOCOL,
+ "Bad multi-bulk length");
+ return REDIS_ERR;
+ }
+
+ root = (r->ridx == 0);
+
+ if (elements < -1 || (LLONG_MAX > SIZE_MAX && elements > SIZE_MAX) ||
+ (r->maxelements > 0 && elements > r->maxelements))
+ {
+ __redisReaderSetError(r,REDIS_ERR_PROTOCOL,
+ "Multi-bulk length out of range");
+ return REDIS_ERR;
+ }
+
+ if (elements == -1) {
+ if (r->fn && r->fn->createNil)
+ obj = r->fn->createNil(cur);
+ else
+ obj = (void*)REDIS_REPLY_NIL;
+
+ if (obj == NULL) {
+ __redisReaderSetErrorOOM(r);
+ return REDIS_ERR;
+ }
+
+ moveToNextTask(r);
+ } else {
+ if (cur->type == REDIS_REPLY_MAP) elements *= 2;
+
+ if (r->fn && r->fn->createArray)
+ obj = r->fn->createArray(cur,elements);
+ else
+ obj = (void*)(long)cur->type;
+
+ if (obj == NULL) {
+ __redisReaderSetErrorOOM(r);
+ return REDIS_ERR;
+ }
+
+ /* Modify task stack when there are more than 0 elements. */
+ if (elements > 0) {
+ cur->elements = elements;
+ cur->obj = obj;
+ r->ridx++;
+ r->task[r->ridx]->type = -1;
+ r->task[r->ridx]->elements = -1;
+ r->task[r->ridx]->idx = 0;
+ r->task[r->ridx]->obj = NULL;
+ r->task[r->ridx]->parent = cur;
+ r->task[r->ridx]->privdata = r->privdata;
+ } else {
+ moveToNextTask(r);
+ }
+ }
+
+ /* Set reply if this is the root object. */
+ if (root) r->reply = obj;
+ return REDIS_OK;
+ }
+
+ return REDIS_ERR;
+}
+
+static int processItem(redisReader *r) {
+ redisReadTask *cur = r->task[r->ridx];
+ char *p;
+
+ /* check if we need to read type */
+ if (cur->type < 0) {
+ if ((p = readBytes(r,1)) != NULL) {
+ switch (p[0]) {
+ case '-':
+ cur->type = REDIS_REPLY_ERROR;
+ break;
+ case '+':
+ cur->type = REDIS_REPLY_STATUS;
+ break;
+ case ':':
+ cur->type = REDIS_REPLY_INTEGER;
+ break;
+ case ',':
+ cur->type = REDIS_REPLY_DOUBLE;
+ break;
+ case '_':
+ cur->type = REDIS_REPLY_NIL;
+ break;
+ case '$':
+ cur->type = REDIS_REPLY_STRING;
+ break;
+ case '*':
+ cur->type = REDIS_REPLY_ARRAY;
+ break;
+ case '%':
+ cur->type = REDIS_REPLY_MAP;
+ break;
+ case '~':
+ cur->type = REDIS_REPLY_SET;
+ break;
+ case '#':
+ cur->type = REDIS_REPLY_BOOL;
+ break;
+ case '=':
+ cur->type = REDIS_REPLY_VERB;
+ break;
+ case '>':
+ cur->type = REDIS_REPLY_PUSH;
+ break;
+ case '(':
+ cur->type = REDIS_REPLY_BIGNUM;
+ break;
+ default:
+ __redisReaderSetErrorProtocolByte(r,*p);
+ return REDIS_ERR;
+ }
+ } else {
+ /* could not consume 1 byte */
+ return REDIS_ERR;
+ }
+ }
+
+ /* process typed item */
+ switch(cur->type) {
+ case REDIS_REPLY_ERROR:
+ case REDIS_REPLY_STATUS:
+ case REDIS_REPLY_INTEGER:
+ 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:
+ return processBulkItem(r);
+ case REDIS_REPLY_ARRAY:
+ case REDIS_REPLY_MAP:
+ case REDIS_REPLY_SET:
+ case REDIS_REPLY_PUSH:
+ return processAggregateItem(r);
+ default:
+ assert(NULL);
+ return REDIS_ERR; /* Avoid warning. */
+ }
+}
+
+redisReader *redisReaderCreateWithFunctions(redisReplyObjectFunctions *fn) {
+ redisReader *r;
+
+ r = hi_calloc(1,sizeof(redisReader));
+ if (r == NULL)
+ return NULL;
+
+ r->buf = hi_sdsempty();
+ if (r->buf == NULL)
+ goto oom;
+
+ r->task = hi_calloc(REDIS_READER_STACK_SIZE, sizeof(*r->task));
+ if (r->task == NULL)
+ goto oom;
+
+ for (; r->tasks < REDIS_READER_STACK_SIZE; r->tasks++) {
+ r->task[r->tasks] = hi_calloc(1, sizeof(**r->task));
+ if (r->task[r->tasks] == NULL)
+ goto oom;
+ }
+
+ r->fn = fn;
+ r->maxbuf = REDIS_READER_MAX_BUF;
+ r->maxelements = REDIS_READER_MAX_ARRAY_ELEMENTS;
+ r->ridx = -1;
+
+ return r;
+oom:
+ redisReaderFree(r);
+ return NULL;
+}
+
+void redisReaderFree(redisReader *r) {
+ if (r == NULL)
+ return;
+
+ if (r->reply != NULL && r->fn && r->fn->freeObject)
+ r->fn->freeObject(r->reply);
+
+ if (r->task) {
+ /* We know r->task[i] is allocated if i < r->tasks */
+ for (int i = 0; i < r->tasks; i++) {
+ hi_free(r->task[i]);
+ }
+
+ hi_free(r->task);
+ }
+
+ hi_sdsfree(r->buf);
+ hi_free(r);
+}
+
+int redisReaderFeed(redisReader *r, const char *buf, size_t len) {
+ hisds newbuf;
+
+ /* Return early when this reader is in an erroneous state. */
+ if (r->err)
+ return REDIS_ERR;
+
+ /* Copy the provided buffer. */
+ if (buf != NULL && len >= 1) {
+ /* Destroy internal buffer when it is empty and is quite large. */
+ if (r->len == 0 && r->maxbuf != 0 && hi_sdsavail(r->buf) > r->maxbuf) {
+ hi_sdsfree(r->buf);
+ r->buf = hi_sdsempty();
+ if (r->buf == 0) goto oom;
+
+ r->pos = 0;
+ }
+
+ newbuf = hi_sdscatlen(r->buf,buf,len);
+ if (newbuf == NULL) goto oom;
+
+ r->buf = newbuf;
+ r->len = hi_sdslen(r->buf);
+ }
+
+ return REDIS_OK;
+oom:
+ __redisReaderSetErrorOOM(r);
+ return REDIS_ERR;
+}
+
+int redisReaderGetReply(redisReader *r, void **reply) {
+ /* Default target pointer to NULL. */
+ if (reply != NULL)
+ *reply = NULL;
+
+ /* Return early when this reader is in an erroneous state. */
+ if (r->err)
+ return REDIS_ERR;
+
+ /* When the buffer is empty, there will never be a reply. */
+ if (r->len == 0)
+ return REDIS_OK;
+
+ /* Set first item to process when the stack is empty. */
+ if (r->ridx == -1) {
+ r->task[0]->type = -1;
+ r->task[0]->elements = -1;
+ r->task[0]->idx = -1;
+ r->task[0]->obj = NULL;
+ r->task[0]->parent = NULL;
+ r->task[0]->privdata = r->privdata;
+ r->ridx = 0;
+ }
+
+ /* Process items in reply. */
+ while (r->ridx >= 0)
+ if (processItem(r) != REDIS_OK)
+ break;
+
+ /* Return ASAP when an error occurred. */
+ if (r->err)
+ return REDIS_ERR;
+
+ /* Discard part of the buffer when we've consumed at least 1k, to avoid
+ * doing unnecessary calls to memmove() in sds.c. */
+ if (r->pos >= 1024) {
+ if (hi_sdsrange(r->buf,r->pos,-1) < 0) return REDIS_ERR;
+ r->pos = 0;
+ r->len = hi_sdslen(r->buf);
+ }
+
+ /* Emit a reply when there is one. */
+ if (r->ridx == -1) {
+ if (reply != NULL) {
+ *reply = r->reply;
+ } else if (r->reply != NULL && r->fn && r->fn->freeObject) {
+ r->fn->freeObject(r->reply);
+ }
+ r->reply = NULL;
+ }
+ return REDIS_OK;
+}
diff --git a/deps/hiredis/read.h b/deps/hiredis/read.h
new file mode 100644
index 0000000..2d74d77
--- /dev/null
+++ b/deps/hiredis/read.h
@@ -0,0 +1,129 @@
+/*
+ * Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com>
+ * Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com>
+ *
+ * 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.
+ */
+
+
+#ifndef __HIREDIS_READ_H
+#define __HIREDIS_READ_H
+#include <stdio.h> /* for size_t */
+
+#define REDIS_ERR -1
+#define REDIS_OK 0
+
+/* When an error occurs, the err flag in a context is set to hold the type of
+ * error that occurred. REDIS_ERR_IO means there was an I/O error and you
+ * should use the "errno" variable to find out what is wrong.
+ * For other values, the "errstr" field will hold a description. */
+#define REDIS_ERR_IO 1 /* Error in read or write */
+#define REDIS_ERR_EOF 3 /* End of file */
+#define REDIS_ERR_PROTOCOL 4 /* Protocol error */
+#define REDIS_ERR_OOM 5 /* Out of memory */
+#define REDIS_ERR_TIMEOUT 6 /* Timed out */
+#define REDIS_ERR_OTHER 2 /* Everything else... */
+
+#define REDIS_REPLY_STRING 1
+#define REDIS_REPLY_ARRAY 2
+#define REDIS_REPLY_INTEGER 3
+#define REDIS_REPLY_NIL 4
+#define REDIS_REPLY_STATUS 5
+#define REDIS_REPLY_ERROR 6
+#define REDIS_REPLY_DOUBLE 7
+#define REDIS_REPLY_BOOL 8
+#define REDIS_REPLY_MAP 9
+#define REDIS_REPLY_SET 10
+#define REDIS_REPLY_ATTR 11
+#define REDIS_REPLY_PUSH 12
+#define REDIS_REPLY_BIGNUM 13
+#define REDIS_REPLY_VERB 14
+
+/* Default max unused reader buffer. */
+#define REDIS_READER_MAX_BUF (1024*16)
+
+/* Default multi-bulk element limit */
+#define REDIS_READER_MAX_ARRAY_ELEMENTS ((1LL<<32) - 1)
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct redisReadTask {
+ int type;
+ long long elements; /* number of elements in multibulk container */
+ int idx; /* index in parent (array) object */
+ void *obj; /* holds user-generated value for a read task */
+ struct redisReadTask *parent; /* parent task */
+ void *privdata; /* user-settable arbitrary field */
+} redisReadTask;
+
+typedef struct redisReplyObjectFunctions {
+ void *(*createString)(const redisReadTask*, char*, size_t);
+ void *(*createArray)(const redisReadTask*, size_t);
+ void *(*createInteger)(const redisReadTask*, long long);
+ void *(*createDouble)(const redisReadTask*, double, char*, size_t);
+ void *(*createNil)(const redisReadTask*);
+ void *(*createBool)(const redisReadTask*, int);
+ void (*freeObject)(void*);
+} redisReplyObjectFunctions;
+
+typedef struct redisReader {
+ int err; /* Error flags, 0 when there is no error */
+ char errstr[128]; /* String representation of error when applicable */
+
+ char *buf; /* Read buffer */
+ size_t pos; /* Buffer cursor */
+ size_t len; /* Buffer length */
+ size_t maxbuf; /* Max length of unused buffer */
+ long long maxelements; /* Max multi-bulk elements */
+
+ redisReadTask **task;
+ int tasks;
+
+ int ridx; /* Index of current read task */
+ void *reply; /* Temporary reply pointer */
+
+ redisReplyObjectFunctions *fn;
+ void *privdata;
+} redisReader;
+
+/* Public API for the protocol parser. */
+redisReader *redisReaderCreateWithFunctions(redisReplyObjectFunctions *fn);
+void redisReaderFree(redisReader *r);
+int redisReaderFeed(redisReader *r, const char *buf, size_t len);
+int redisReaderGetReply(redisReader *r, void **reply);
+
+#define redisReaderSetPrivdata(_r, _p) (int)(((redisReader*)(_r))->privdata = (_p))
+#define redisReaderGetObject(_r) (((redisReader*)(_r))->reply)
+#define redisReaderGetError(_r) (((redisReader*)(_r))->errstr)
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/deps/hiredis/sds.c b/deps/hiredis/sds.c
new file mode 100644
index 0000000..114fa49
--- /dev/null
+++ b/deps/hiredis/sds.c
@@ -0,0 +1,1289 @@
+/* SDSLib 2.0 -- A C dynamic strings library
+ *
+ * Copyright (c) 2006-2015, Salvatore Sanfilippo <antirez at gmail dot com>
+ * Copyright (c) 2015, Oran Agra
+ * Copyright (c) 2015, Redis Labs, Inc
+ * 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 "fmacros.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <assert.h>
+#include <limits.h>
+#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);
+ }
+ return 0;
+}
+
+static inline char hi_sdsReqType(size_t string_size) {
+ if (string_size < 32)
+ return HI_SDS_TYPE_5;
+ if (string_size < 0xff)
+ return HI_SDS_TYPE_8;
+ if (string_size < 0xffff)
+ return HI_SDS_TYPE_16;
+ if (string_size < 0xffffffff)
+ return HI_SDS_TYPE_32;
+ return HI_SDS_TYPE_64;
+}
+
+/* Create a new hisds string with the content specified by the 'init' pointer
+ * and 'initlen'.
+ * If NULL is used for 'init' the string is initialized with zero bytes.
+ *
+ * The string is always null-terminated (all the hisds strings are, always) so
+ * even if you create an hisds string with:
+ *
+ * mystring = hi_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) {
+ void *sh;
+ hisds s;
+ char type = hi_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);
+ unsigned char *fp; /* flags pointer. */
+
+ sh = hi_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);
+ break;
+ }
+ case HI_SDS_TYPE_8: {
+ HI_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);
+ sh->len = initlen;
+ sh->alloc = initlen;
+ *fp = type;
+ break;
+ }
+ case HI_SDS_TYPE_32: {
+ HI_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);
+ sh->len = initlen;
+ sh->alloc = initlen;
+ *fp = type;
+ break;
+ }
+ }
+ if (initlen && init)
+ memcpy(s, init, initlen);
+ s[initlen] = '\0';
+ return s;
+}
+
+/* Create an empty (zero length) hisds string. Even in this case the string
+ * always has an implicit null term. */
+hisds hi_sdsempty(void) {
+ return hi_sdsnewlen("",0);
+}
+
+/* Create a new hisds string starting from a null terminated C string. */
+hisds hi_sdsnew(const char *init) {
+ size_t initlen = (init == NULL) ? 0 : strlen(init);
+ return hi_sdsnewlen(init, initlen);
+}
+
+/* Duplicate an hisds string. */
+hisds hi_sdsdup(const hisds s) {
+ return hi_sdsnewlen(s, hi_sdslen(s));
+}
+
+/* Free an hisds string. No operation is performed if 's' is NULL. */
+void hi_sdsfree(hisds s) {
+ if (s == NULL) return;
+ hi_s_free((char*)s-hi_sdsHdrSize(s[-1]));
+}
+
+/* Set the hisds 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
+ * way, like in the following example:
+ *
+ * s = hi_sdsnew("foobar");
+ * s[2] = '\0';
+ * hi_sdsupdatelen(s);
+ * printf("%d\n", hi_sdslen(s));
+ *
+ * The output will be "2", but if we comment out the call to hi_sdsupdatelen()
+ * the output will be "6" as the string was modified but the logical length
+ * remains 6 bytes. */
+void hi_sdsupdatelen(hisds s) {
+ int reallen = strlen(s);
+ hi_sdssetlen(s, reallen);
+}
+
+/* Modify an hisds 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);
+ s[0] = '\0';
+}
+
+/* Enlarge the free space at the end of the hisds 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) {
+ void *sh, *newsh;
+ size_t avail = hi_sdsavail(s);
+ size_t len, newlen;
+ char type, oldtype = s[-1] & HI_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);
+ newlen = (len+addlen);
+ if (newlen < HI_SDS_MAX_PREALLOC)
+ newlen *= 2;
+ else
+ newlen += HI_SDS_MAX_PREALLOC;
+
+ type = hi_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
+ * at every appending operation. */
+ if (type == HI_SDS_TYPE_5) type = HI_SDS_TYPE_8;
+
+ hdrlen = hi_sdsHdrSize(type);
+ if (oldtype==type) {
+ newsh = hi_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);
+ if (newsh == NULL) return NULL;
+ memcpy((char*)newsh+hdrlen, s, len+1);
+ hi_s_free(sh);
+ s = (char*)newsh+hdrlen;
+ s[-1] = type;
+ hi_sdssetlen(s, len);
+ }
+ hi_sdssetalloc(s, newlen);
+ return s;
+}
+
+/* Reallocate the hisds 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
+ * references must be substituted with the new pointer returned by the call. */
+hisds hi_sdsRemoveFreeSpace(hisds s) {
+ void *sh, *newsh;
+ char type, oldtype = s[-1] & HI_SDS_TYPE_MASK;
+ int hdrlen;
+ size_t len = hi_sdslen(s);
+ sh = (char*)s-hi_sdsHdrSize(oldtype);
+
+ type = hi_sdsReqType(len);
+ hdrlen = hi_sdsHdrSize(type);
+ if (oldtype==type) {
+ newsh = hi_s_realloc(sh, hdrlen+len+1);
+ if (newsh == NULL) return NULL;
+ s = (char*)newsh+hdrlen;
+ } else {
+ newsh = hi_s_malloc(hdrlen+len+1);
+ if (newsh == NULL) return NULL;
+ memcpy((char*)newsh+hdrlen, s, len+1);
+ hi_s_free(sh);
+ s = (char*)newsh+hdrlen;
+ s[-1] = type;
+ hi_sdssetlen(s, len);
+ }
+ hi_sdssetalloc(s, len);
+ return s;
+}
+
+/* Return the total size of the allocation of the specifed hisds string,
+ * including:
+ * 1) The hisds 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;
+}
+
+/* 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]));
+}
+
+/* Increment the hisds 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
+ * the current string, and finally needs to set the new length.
+ *
+ * Note: it is possible to use a negative increment in order to
+ * right-trim the string.
+ *
+ * Usage example:
+ *
+ * Using hi_sdsIncrLen() and hi_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:
+ *
+ * oldlen = hi_hi_sdslen(s);
+ * s = hi_sdsMakeRoomFor(s, BUFFER_SIZE);
+ * nread = read(fd, s+oldlen, BUFFER_SIZE);
+ * ... check for nread <= 0 and handle it ...
+ * hi_sdsIncrLen(s, nread);
+ */
+void hi_sdsIncrLen(hisds s, int incr) {
+ unsigned char flags = s[-1];
+ size_t len;
+ switch(flags&HI_SDS_TYPE_MASK) {
+ case HI_SDS_TYPE_5: {
+ unsigned char *fp = ((unsigned char*)s)-1;
+ unsigned char oldlen = HI_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);
+ len = oldlen+incr;
+ break;
+ }
+ case HI_SDS_TYPE_8: {
+ HI_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);
+ 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);
+ 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);
+ assert((incr >= 0 && sh->alloc-sh->len >= (uint64_t)incr) || (incr < 0 && sh->len >= (uint64_t)(-incr)));
+ len = (sh->len += incr);
+ break;
+ }
+ default: len = 0; /* Just to avoid compilation warnings. */
+ }
+ s[len] = '\0';
+}
+
+/* Grow the hisds to have the specified length. Bytes that were not part of
+ * the original length of the hisds 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);
+
+ if (len <= curlen) return s;
+ s = hi_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);
+ return s;
+}
+
+/* Append the specified binary-safe string pointed by 't' of 'len' bytes to the
+ * end of the specified hisds string 's'.
+ *
+ * After the call, the passed hisds 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);
+
+ s = hi_sdsMakeRoomFor(s,len);
+ if (s == NULL) return NULL;
+ memcpy(s+curlen, t, len);
+ hi_sdssetlen(s, curlen+len);
+ s[curlen+len] = '\0';
+ return s;
+}
+
+/* Append the specified null termianted C string to the hisds string 's'.
+ *
+ * After the call, the passed hisds 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));
+}
+
+/* Append the specified hisds 't' to the existing hisds 's'.
+ *
+ * After the call, the modified hisds 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));
+}
+
+/* Destructively modify the hisds 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));
+ if (s == NULL) return NULL;
+ }
+ memcpy(s, t, len);
+ s[len] = '\0';
+ hi_sdssetlen(s, len);
+ return s;
+}
+
+/* Like hi_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));
+}
+
+/* Helper for hi_sdscatlonglong() doing the actual number -> string
+ * conversion. 's' must point to a string with room for at least
+ * HI_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) {
+ char *p, aux;
+ unsigned long long v;
+ size_t l;
+
+ /* Generate the string representation, this method produces
+ * an reversed string. */
+ v = (value < 0) ? -value : value;
+ p = s;
+ do {
+ *p++ = '0'+(v%10);
+ v /= 10;
+ } while(v);
+ if (value < 0) *p++ = '-';
+
+ /* Compute length and add null term. */
+ l = p-s;
+ *p = '\0';
+
+ /* Reverse the string. */
+ p--;
+ while(s < p) {
+ aux = *s;
+ *s = *p;
+ *p = aux;
+ s++;
+ p--;
+ }
+ return l;
+}
+
+/* Identical hi_sdsll2str(), but for unsigned long long type. */
+int hi_sdsull2str(char *s, unsigned long long v) {
+ char *p, aux;
+ size_t l;
+
+ /* Generate the string representation, this method produces
+ * an reversed string. */
+ p = s;
+ do {
+ *p++ = '0'+(v%10);
+ v /= 10;
+ } while(v);
+
+ /* Compute length and add null term. */
+ l = p-s;
+ *p = '\0';
+
+ /* Reverse the string. */
+ p--;
+ while(s < p) {
+ aux = *s;
+ *s = *p;
+ *p = aux;
+ s++;
+ p--;
+ }
+ return l;
+}
+
+/* Create an hisds string from a long long value. It is much faster than:
+ *
+ * hi_sdscatprintf(hi_sdsempty(),"%lld\n", value);
+ */
+hisds hi_sdsfromlonglong(long long value) {
+ char buf[HI_SDS_LLSTR_SIZE];
+ int len = hi_sdsll2str(buf,value);
+
+ return hi_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) {
+ va_list cpy;
+ char staticbuf[1024], *buf = staticbuf, *t;
+ size_t buflen = strlen(fmt)*2;
+
+ /* We try to start using a static buffer for speed.
+ * If not possible we revert to heap allocation. */
+ if (buflen > sizeof(staticbuf)) {
+ buf = hi_s_malloc(buflen);
+ if (buf == NULL) return NULL;
+ } else {
+ buflen = sizeof(staticbuf);
+ }
+
+ /* Try with buffers two times bigger every time we fail to
+ * fit the string in the current buffer size. */
+ while(1) {
+ buf[buflen-2] = '\0';
+ va_copy(cpy,ap);
+ vsnprintf(buf, buflen, fmt, cpy);
+ va_end(cpy);
+ if (buf[buflen-2] != '\0') {
+ if (buf != staticbuf) hi_s_free(buf);
+ buflen *= 2;
+ buf = hi_s_malloc(buflen);
+ if (buf == NULL) return NULL;
+ continue;
+ }
+ break;
+ }
+
+ /* Finally concat the obtained string to the SDS string and return it. */
+ t = hi_sdscat(s, buf);
+ if (buf != staticbuf) hi_s_free(buf);
+ return t;
+}
+
+/* Append to the hisds string 's' a string obtained using printf-alike format
+ * specifier.
+ *
+ * After the call, the modified hisds 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).
+ *
+ * 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:
+ *
+ * s = hi_sdscatprintf(hi_sdsempty(), "... your format ...", args);
+ */
+hisds hi_sdscatprintf(hisds s, const char *fmt, ...) {
+ va_list ap;
+ char *t;
+ va_start(ap, fmt);
+ t = hi_sdscatvprintf(s,fmt,ap);
+ va_end(ap);
+ return t;
+}
+
+/* This function is similar to hi_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
+ * new data is concatenated provides a performance improvement.
+ *
+ * However this function only handles an incompatible subset of printf-alike
+ * format specifiers:
+ *
+ * %s - C String
+ * %S - SDS string
+ * %i - signed int
+ * %I - 64 bit signed integer (long long, int64_t)
+ * %u - unsigned int
+ * %U - 64 bit unsigned integer (unsigned long long, uint64_t)
+ * %% - Verbatim "%" character.
+ */
+hisds hi_sdscatfmt(hisds 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. */
+ while(*f) {
+ char next, *str;
+ size_t l;
+ long long num;
+ unsigned long long unum;
+
+ /* Make sure there is always space for at least 1 char. */
+ if (hi_sdsavail(s)==0) {
+ s = hi_sdsMakeRoomFor(s,1);
+ if (s == NULL) goto fmt_error;
+ }
+
+ switch(*f) {
+ case '%':
+ next = *(f+1);
+ f++;
+ switch(next) {
+ case 's':
+ case 'S':
+ str = va_arg(ap,char*);
+ l = (next == 's') ? strlen(str) : hi_sdslen(str);
+ if (hi_sdsavail(s) < l) {
+ s = hi_sdsMakeRoomFor(s,l);
+ if (s == NULL) goto fmt_error;
+ }
+ memcpy(s+i,str,l);
+ hi_sdsinclen(s,l);
+ i += l;
+ break;
+ case 'i':
+ case 'I':
+ if (next == 'i')
+ num = va_arg(ap,int);
+ else
+ num = va_arg(ap,long long);
+ {
+ char buf[HI_SDS_LLSTR_SIZE];
+ l = hi_sdsll2str(buf,num);
+ if (hi_sdsavail(s) < l) {
+ s = hi_sdsMakeRoomFor(s,l);
+ if (s == NULL) goto fmt_error;
+ }
+ memcpy(s+i,buf,l);
+ hi_sdsinclen(s,l);
+ i += l;
+ }
+ break;
+ case 'u':
+ case 'U':
+ if (next == 'u')
+ unum = va_arg(ap,unsigned int);
+ else
+ unum = va_arg(ap,unsigned long long);
+ {
+ char buf[HI_SDS_LLSTR_SIZE];
+ l = hi_sdsull2str(buf,unum);
+ if (hi_sdsavail(s) < l) {
+ s = hi_sdsMakeRoomFor(s,l);
+ if (s == NULL) goto fmt_error;
+ }
+ memcpy(s+i,buf,l);
+ hi_sdsinclen(s,l);
+ i += l;
+ }
+ break;
+ default: /* Handle %% and generally %<unknown>. */
+ s[i++] = next;
+ hi_sdsinclen(s,1);
+ break;
+ }
+ break;
+ default:
+ s[i++] = *f;
+ hi_sdsinclen(s,1);
+ break;
+ }
+ f++;
+ }
+ va_end(ap);
+
+ /* Add null-term */
+ s[i] = '\0';
+ return s;
+
+fmt_error:
+ va_end(ap);
+ return NULL;
+}
+
+/* Remove the part of the string from left and from right composed just of
+ * contiguous characters found in 'cset', that is a null terminted C string.
+ *
+ * After the call, the modified hisds 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. :");
+ * printf("%s\n", s);
+ *
+ * Output will be just "Hello World".
+ */
+hisds hi_sdstrim(hisds s, const char *cset) {
+ char *start, *end, *sp, *ep;
+ size_t len;
+
+ sp = start = s;
+ ep = end = s+hi_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);
+ return s;
+}
+
+/* Turn the string into a smaller (or equal) string containing only the
+ * substring specified by the 'start' and 'end' indexes.
+ *
+ * start and end can be negative, where -1 means the last character of the
+ * string, -2 the penultimate character, and so forth.
+ *
+ * The interval is inclusive, so the start and end characters will be part
+ * of the resulting string.
+ *
+ * The string is modified in-place.
+ *
+ * Return value:
+ * -1 (error) if hi_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"
+ */
+int hi_sdsrange(hisds s, ssize_t start, ssize_t end) {
+ size_t newlen, len = hi_sdslen(s);
+ if (len > SSIZE_MAX) return -1;
+
+ if (len == 0) return 0;
+ if (start < 0) {
+ start = len+start;
+ if (start < 0) start = 0;
+ }
+ if (end < 0) {
+ end = len+end;
+ if (end < 0) end = 0;
+ }
+ newlen = (start > end) ? 0 : (end-start)+1;
+ if (newlen != 0) {
+ if (start >= (ssize_t)len) {
+ newlen = 0;
+ } else if (end >= (ssize_t)len) {
+ end = len-1;
+ newlen = (start > end) ? 0 : (end-start)+1;
+ }
+ } else {
+ start = 0;
+ }
+ if (start && newlen) memmove(s, s+start, newlen);
+ s[newlen] = 0;
+ hi_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;
+
+ 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;
+
+ for (j = 0; j < len; j++) s[j] = toupper(s[j]);
+}
+
+/* Compare two hisds strings s1 and s2 with memcmp().
+ *
+ * Return value:
+ *
+ * positive if s1 > s2.
+ * negative if s1 < s2.
+ * 0 if s1 and s2 are exactly the same binary string.
+ *
+ * If two strings share exactly the same prefix, but one of the two has
+ * additional characters, the longer string is considered to be greater than
+ * the smaller one. */
+int hi_sdscmp(const hisds s1, const hisds s2) {
+ size_t l1, l2, minlen;
+ int cmp;
+
+ l1 = hi_sdslen(s1);
+ l2 = hi_sdslen(s2);
+ minlen = (l1 < l2) ? l1 : l2;
+ cmp = memcmp(s1,s2,minlen);
+ if (cmp == 0) return l1-l2;
+ return cmp;
+}
+
+/* Split 's' with separator in 'sep'. An array
+ * of hisds strings is returned. *count will be set
+ * by reference to the number of tokens returned.
+ *
+ * On out of memory, zero length string, zero length
+ * separator, NULL is returned.
+ *
+ * Note that 'sep' is able to split a string using
+ * a multi-character separator. For example
+ * hi_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
+ * same function but for zero-terminated strings.
+ */
+hisds *hi_sdssplitlen(const char *s, int len, const char *sep, int seplen, int *count) {
+ int elements = 0, slots = 5, start = 0, j;
+ hisds *tokens;
+
+ if (seplen < 1 || len < 0) return NULL;
+
+ tokens = hi_s_malloc(sizeof(hisds)*slots);
+ if (tokens == NULL) return NULL;
+
+ if (len == 0) {
+ *count = 0;
+ return tokens;
+ }
+ for (j = 0; j < (len-(seplen-1)); j++) {
+ /* make sure there is room for the next element and the final one */
+ if (slots < elements+2) {
+ hisds *newtokens;
+
+ slots *= 2;
+ newtokens = hi_s_realloc(tokens,sizeof(hisds)*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);
+ if (tokens[elements] == NULL) goto cleanup;
+ elements++;
+ start = j+seplen;
+ j = j+seplen-1; /* skip the separator */
+ }
+ }
+ /* Add the final element. We are sure there is room in the tokens array. */
+ tokens[elements] = hi_sdsnewlen(s+start,len-start);
+ if (tokens[elements] == NULL) goto cleanup;
+ elements++;
+ *count = elements;
+ return tokens;
+
+cleanup:
+ {
+ int i;
+ for (i = 0; i < elements; i++) hi_sdsfree(tokens[i]);
+ hi_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) {
+ if (!tokens) return;
+ while(count--)
+ hi_sdsfree(tokens[count]);
+ hi_s_free(tokens);
+}
+
+/* Append to the hisds 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<hex-number>".
+ *
+ * After the call, the modified hisds 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);
+ while(len--) {
+ switch(*p) {
+ case '\\':
+ case '"':
+ s = hi_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;
+ default:
+ if (isprint(*p))
+ s = hi_sdscatprintf(s,"%c",*p);
+ else
+ s = hi_sdscatprintf(s,"\\x%02x",(unsigned char)*p);
+ break;
+ }
+ p++;
+ }
+ return hi_sdscatlen(s,"\"",1);
+}
+
+/* Helper function for hi_sdssplitargs() that converts a hex digit into an
+ * integer from 0 to 15 */
+static int hi_hex_digit_to_int(char c) {
+ switch(c) {
+ case '0': return 0;
+ case '1': return 1;
+ case '2': return 2;
+ case '3': return 3;
+ case '4': return 4;
+ case '5': return 5;
+ case '6': return 6;
+ case '7': return 7;
+ case '8': return 8;
+ case '9': return 9;
+ case 'a': case 'A': return 10;
+ case 'b': case 'B': return 11;
+ case 'c': case 'C': return 12;
+ case 'd': case 'D': return 13;
+ case 'e': case 'E': return 14;
+ case 'f': case 'F': return 15;
+ default: return 0;
+ }
+}
+
+/* Split a line into arguments, where every argument can be in the
+ * following programming-language REPL-alike form:
+ *
+ * foo bar "newline are supported\n" and "\xff\x00otherstuff"
+ *
+ * The number of arguments is stored into *argc, and an array
+ * of hisds is returned.
+ *
+ * The caller should free the resulting array of hisds strings with
+ * hi_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.
+ *
+ * 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) {
+ const char *p = line;
+ char *current = NULL;
+ char **vector = NULL;
+
+ *argc = 0;
+ while(1) {
+ /* skip blanks */
+ while(*p && isspace(*p)) p++;
+ if (*p) {
+ /* get a token */
+ int inq=0; /* set to 1 if we are in "quotes" */
+ int insq=0; /* set to 1 if we are in 'single quotes' */
+ int done=0;
+
+ if (current == NULL) current = hi_sdsempty();
+ while(!done) {
+ if (inq) {
+ if (*p == '\\' && *(p+1) == 'x' &&
+ isxdigit(*(p+2)) &&
+ isxdigit(*(p+3)))
+ {
+ 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);
+ p += 3;
+ } else if (*p == '\\' && *(p+1)) {
+ char c;
+
+ p++;
+ switch(*p) {
+ case 'n': c = '\n'; break;
+ case 'r': c = '\r'; break;
+ case 't': c = '\t'; break;
+ case 'b': c = '\b'; break;
+ case 'a': c = '\a'; break;
+ default: c = *p; break;
+ }
+ current = hi_sdscatlen(current,&c,1);
+ } else if (*p == '"') {
+ /* closing quote must be followed by a space or
+ * nothing at all. */
+ if (*(p+1) && !isspace(*(p+1))) goto err;
+ done=1;
+ } else if (!*p) {
+ /* unterminated quotes */
+ goto err;
+ } else {
+ current = hi_sdscatlen(current,p,1);
+ }
+ } else if (insq) {
+ if (*p == '\\' && *(p+1) == '\'') {
+ p++;
+ current = hi_sdscatlen(current,"'",1);
+ } else if (*p == '\'') {
+ /* closing quote must be followed by a space or
+ * nothing at all. */
+ if (*(p+1) && !isspace(*(p+1))) goto err;
+ done=1;
+ } else if (!*p) {
+ /* unterminated quotes */
+ goto err;
+ } else {
+ current = hi_sdscatlen(current,p,1);
+ }
+ } else {
+ switch(*p) {
+ case ' ':
+ case '\n':
+ case '\r':
+ case '\t':
+ case '\0':
+ done=1;
+ break;
+ case '"':
+ inq=1;
+ break;
+ case '\'':
+ insq=1;
+ break;
+ default:
+ current = hi_sdscatlen(current,p,1);
+ break;
+ }
+ }
+ if (*p) p++;
+ }
+ /* add the token to the vector */
+ {
+ char **new_vector = hi_s_realloc(vector,((*argc)+1)*sizeof(char*));
+ if (new_vector == NULL) {
+ hi_s_free(vector);
+ return NULL;
+ }
+
+ vector = new_vector;
+ vector[*argc] = current;
+ (*argc)++;
+ current = NULL;
+ }
+ } else {
+ /* Even on empty input string return something not NULL. */
+ if (vector == NULL) vector = hi_s_malloc(sizeof(void*));
+ return vector;
+ }
+ }
+
+err:
+ while((*argc)--)
+ hi_sdsfree(vector[*argc]);
+ hi_s_free(vector);
+ if (current) hi_sdsfree(current);
+ *argc = 0;
+ return NULL;
+}
+
+/* Modify the string substituting all the occurrences of the set of
+ * characters specified in the 'from' string to the corresponding character
+ * in the 'to' array.
+ *
+ * For instance: hi_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
+ * 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);
+
+ for (j = 0; j < l; j++) {
+ for (i = 0; i < setlen; i++) {
+ if (s[j] == from[i]) {
+ s[j] = to[i];
+ break;
+ }
+ }
+ }
+ return s;
+}
+
+/* Join an array of C strings using the specified separator (also a C string).
+ * Returns the result as an hisds string. */
+hisds hi_sdsjoin(char **argv, int argc, char *sep) {
+ hisds join = hi_sdsempty();
+ int j;
+
+ for (j = 0; j < argc; j++) {
+ join = hi_sdscat(join, argv[j]);
+ if (j != argc-1) join = hi_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();
+ int j;
+
+ for (j = 0; j < argc; j++) {
+ join = hi_sdscatsds(join, argv[j]);
+ if (j != argc-1) join = hi_sdscatlen(join,sep,seplen);
+ }
+ return join;
+}
+
+/* Wrappers to the allocators used by SDS. Note that SDS will actually
+ * just use the macros defined into sdsalloc.h in order to avoid to pay
+ * the overhead of function calls. Here we define these wrappers only for
+ * the programs SDS is linked to, if they want to touch the SDS internals
+ * even if they use a different allocator. */
+void *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); }
+
+#if defined(HI_SDS_TEST_MAIN)
+#include <stdio.h>
+#include "testhelp.h"
+#include "limits.h"
+
+#define UNUSED(x) (void)(x)
+int hi_sdsTest(void) {
+ {
+ hisds x = hi_sdsnew("foo"), y;
+
+ test_cond("Create a string and obtain the length",
+ hi_sdslen(x) == 3 && memcmp(x,"foo\0",4) == 0)
+
+ hi_sdsfree(x);
+ x = hi_sdsnewlen("foo",2);
+ test_cond("Create a string with specified length",
+ hi_sdslen(x) == 2 && memcmp(x,"fo\0",3) == 0)
+
+ x = hi_sdscat(x,"bar");
+ test_cond("Strings concatenation",
+ hi_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 = hi_sdscpy(x,"xyzxxxxxxxxxxyyyyyyyyyykkkkkkkkkk");
+ test_cond("hi_sdscpy() against an originally shorter string",
+ hi_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)
+
+ 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 &&
+ 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 &&
+ 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...)",
+ memcmp(y,"\"\\a\\n\\x00foo\\r\"",15) == 0)
+
+ {
+ unsigned int oldfree;
+ 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);
+
+ /* 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);
+ }
+ p = x+oldlen;
+ for (j = 0; j < step; j++) {
+ p[j] = 'A'+j;
+ }
+ hi_sdsIncrLen(x,step);
+ }
+ test_cond("hi_sdsMakeRoomFor() content",
+ memcmp("0ABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJ",x,101) == 0);
+ test_cond("sdsMakeRoomFor() final length",hi_sdslen(x)==101);
+
+ hi_sdsfree(x);
+ }
+ }
+ test_report();
+ return 0;
+}
+#endif
+
+#ifdef HI_SDS_TEST_MAIN
+int main(void) {
+ return hi_sdsTest();
+}
+#endif
diff --git a/deps/hiredis/sds.h b/deps/hiredis/sds.h
new file mode 100644
index 0000000..573d6dd
--- /dev/null
+++ b/deps/hiredis/sds.h
@@ -0,0 +1,278 @@
+/* SDSLib 2.0 -- A C dynamic strings library
+ *
+ * Copyright (c) 2006-2015, Salvatore Sanfilippo <antirez at gmail dot com>
+ * Copyright (c) 2015, Oran Agra
+ * Copyright (c) 2015, Redis Labs, Inc
+ * 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.
+ */
+
+#ifndef HIREDIS_SDS_H
+#define HIREDIS_SDS_H
+
+#define HI_SDS_MAX_PREALLOC (1024*1024)
+#ifdef _MSC_VER
+#define __attribute__(x)
+typedef long long ssize_t;
+#define SSIZE_MAX (LLONG_MAX >> 1)
+#endif
+
+#include <sys/types.h>
+#include <stdarg.h>
+#include <stdint.h>
+
+typedef char *hisds;
+
+/* 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 {
+ unsigned char flags; /* 3 lsb of type, and 5 msb of string length */
+ char buf[];
+};
+struct __attribute__ ((__packed__)) hisdshdr8 {
+ 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 {
+ 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 {
+ 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 {
+ 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)
+
+static inline size_t hi_sdslen(const hisds 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;
+ }
+ return 0;
+}
+
+static inline size_t hi_sdsavail(const hisds s) {
+ unsigned char flags = s[-1];
+ switch(flags&HI_SDS_TYPE_MASK) {
+ case HI_SDS_TYPE_5: {
+ return 0;
+ }
+ case HI_SDS_TYPE_8: {
+ HI_SDS_HDR_VAR(8,s);
+ return sh->alloc - sh->len;
+ }
+ case HI_SDS_TYPE_16: {
+ HI_SDS_HDR_VAR(16,s);
+ return sh->alloc - sh->len;
+ }
+ case HI_SDS_TYPE_32: {
+ HI_SDS_HDR_VAR(32,s);
+ return sh->alloc - sh->len;
+ }
+ case HI_SDS_TYPE_64: {
+ HI_SDS_HDR_VAR(64,s);
+ return sh->alloc - sh->len;
+ }
+ }
+ return 0;
+}
+
+static inline void hi_sdssetlen(hisds s, size_t newlen) {
+ unsigned char flags = s[-1];
+ switch(flags&HI_SDS_TYPE_MASK) {
+ case HI_SDS_TYPE_5:
+ {
+ unsigned char *fp = ((unsigned char*)s)-1;
+ *fp = (unsigned char)(HI_SDS_TYPE_5 | (newlen << HI_SDS_TYPE_BITS));
+ }
+ break;
+ case HI_SDS_TYPE_8:
+ HI_SDS_HDR(8,s)->len = (uint8_t)newlen;
+ break;
+ case HI_SDS_TYPE_16:
+ HI_SDS_HDR(16,s)->len = (uint16_t)newlen;
+ break;
+ case HI_SDS_TYPE_32:
+ HI_SDS_HDR(32,s)->len = (uint32_t)newlen;
+ break;
+ case HI_SDS_TYPE_64:
+ HI_SDS_HDR(64,s)->len = (uint64_t)newlen;
+ break;
+ }
+}
+
+static inline void hi_sdsinclen(hisds s, size_t inc) {
+ unsigned char flags = s[-1];
+ switch(flags&HI_SDS_TYPE_MASK) {
+ case HI_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);
+ }
+ break;
+ case HI_SDS_TYPE_8:
+ HI_SDS_HDR(8,s)->len += (uint8_t)inc;
+ break;
+ case HI_SDS_TYPE_16:
+ HI_SDS_HDR(16,s)->len += (uint16_t)inc;
+ break;
+ case HI_SDS_TYPE_32:
+ HI_SDS_HDR(32,s)->len += (uint32_t)inc;
+ break;
+ case HI_SDS_TYPE_64:
+ HI_SDS_HDR(64,s)->len += (uint64_t)inc;
+ break;
+ }
+}
+
+/* hi_sdsalloc() = hi_sdsavail() + hi_sdslen() */
+static inline size_t hi_sdsalloc(const hisds 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;
+ }
+ return 0;
+}
+
+static inline void hi_sdssetalloc(hisds s, size_t newlen) {
+ unsigned char flags = s[-1];
+ switch(flags&HI_SDS_TYPE_MASK) {
+ case HI_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;
+ break;
+ case HI_SDS_TYPE_16:
+ HI_SDS_HDR(16,s)->alloc = (uint16_t)newlen;
+ break;
+ case HI_SDS_TYPE_32:
+ HI_SDS_HDR(32,s)->alloc = (uint32_t)newlen;
+ break;
+ case HI_SDS_TYPE_64:
+ HI_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);
+
+hisds hi_sdscatvprintf(hisds s, const char *fmt, va_list ap);
+#ifdef __GNUC__
+hisds hi_sdscatprintf(hisds s, const char *fmt, ...)
+ __attribute__((format(printf, 2, 3)));
+#else
+hisds hi_sdscatprintf(hisds 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);
+
+/* 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);
+
+/* 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);
+
+#ifdef REDIS_TEST
+int hi_sdsTest(int argc, char *argv[]);
+#endif
+
+#endif /* HIREDIS_SDS_H */
diff --git a/deps/hiredis/sdsalloc.h b/deps/hiredis/sdsalloc.h
new file mode 100644
index 0000000..c9dcc3d
--- /dev/null
+++ b/deps/hiredis/sdsalloc.h
@@ -0,0 +1,44 @@
+/* SDSLib 2.0 -- A C dynamic strings library
+ *
+ * Copyright (c) 2006-2015, Salvatore Sanfilippo <antirez at gmail dot com>
+ * Copyright (c) 2015, Oran Agra
+ * Copyright (c) 2015, Redis Labs, Inc
+ * 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 allocator selection.
+ *
+ * This file is used in order to change the SDS allocator at compile time.
+ * Just define the following defines to what you want to use. Also add
+ * the include of your alternate allocator if needed (not needed in order
+ * to use the default libc allocator). */
+
+#include "alloc.h"
+
+#define hi_s_malloc hi_malloc
+#define hi_s_realloc hi_realloc
+#define hi_s_free hi_free
diff --git a/deps/hiredis/sdscompat.h b/deps/hiredis/sdscompat.h
new file mode 100644
index 0000000..e5a2574
--- /dev/null
+++ b/deps/hiredis/sdscompat.h
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 2020, Michael Grunder <michael dot grunder at gmail dot com>
+ *
+ * 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/deps/hiredis/sockcompat.c b/deps/hiredis/sockcompat.c
new file mode 100644
index 0000000..f99d14b
--- /dev/null
+++ b/deps/hiredis/sockcompat.c
@@ -0,0 +1,248 @@
+/*
+ * Copyright (c) 2019, Marcus Geelnard <m at bitsnbites dot eu>
+ *
+ * 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.
+ */
+
+#define REDIS_SOCKCOMPAT_IMPLEMENTATION
+#include "sockcompat.h"
+
+#ifdef _WIN32
+static int _wsaErrorToErrno(int err) {
+ switch (err) {
+ case WSAEWOULDBLOCK:
+ return EWOULDBLOCK;
+ case WSAEINPROGRESS:
+ return EINPROGRESS;
+ case WSAEALREADY:
+ return EALREADY;
+ case WSAENOTSOCK:
+ return ENOTSOCK;
+ case WSAEDESTADDRREQ:
+ return EDESTADDRREQ;
+ case WSAEMSGSIZE:
+ return EMSGSIZE;
+ case WSAEPROTOTYPE:
+ return EPROTOTYPE;
+ case WSAENOPROTOOPT:
+ return ENOPROTOOPT;
+ case WSAEPROTONOSUPPORT:
+ return EPROTONOSUPPORT;
+ case WSAEOPNOTSUPP:
+ return EOPNOTSUPP;
+ case WSAEAFNOSUPPORT:
+ return EAFNOSUPPORT;
+ case WSAEADDRINUSE:
+ return EADDRINUSE;
+ case WSAEADDRNOTAVAIL:
+ return EADDRNOTAVAIL;
+ case WSAENETDOWN:
+ return ENETDOWN;
+ case WSAENETUNREACH:
+ return ENETUNREACH;
+ case WSAENETRESET:
+ return ENETRESET;
+ case WSAECONNABORTED:
+ return ECONNABORTED;
+ case WSAECONNRESET:
+ return ECONNRESET;
+ case WSAENOBUFS:
+ return ENOBUFS;
+ case WSAEISCONN:
+ return EISCONN;
+ case WSAENOTCONN:
+ return ENOTCONN;
+ case WSAETIMEDOUT:
+ return ETIMEDOUT;
+ case WSAECONNREFUSED:
+ return ECONNREFUSED;
+ case WSAELOOP:
+ return ELOOP;
+ case WSAENAMETOOLONG:
+ return ENAMETOOLONG;
+ case WSAEHOSTUNREACH:
+ return EHOSTUNREACH;
+ case WSAENOTEMPTY:
+ return ENOTEMPTY;
+ default:
+ /* We just return a generic I/O error if we could not find a relevant error. */
+ return EIO;
+ }
+}
+
+static void _updateErrno(int success) {
+ errno = success ? 0 : _wsaErrorToErrno(WSAGetLastError());
+}
+
+static int _initWinsock() {
+ static int s_initialized = 0;
+ if (!s_initialized) {
+ static WSADATA wsadata;
+ int err = WSAStartup(MAKEWORD(2,2), &wsadata);
+ if (err != 0) {
+ errno = _wsaErrorToErrno(err);
+ return 0;
+ }
+ s_initialized = 1;
+ }
+ return 1;
+}
+
+int win32_getaddrinfo(const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res) {
+ /* Note: This function is likely to be called before other functions, so run init here. */
+ if (!_initWinsock()) {
+ return EAI_FAIL;
+ }
+
+ switch (getaddrinfo(node, service, hints, res)) {
+ case 0: return 0;
+ case WSATRY_AGAIN: return EAI_AGAIN;
+ case WSAEINVAL: return EAI_BADFLAGS;
+ case WSAEAFNOSUPPORT: return EAI_FAMILY;
+ case WSA_NOT_ENOUGH_MEMORY: return EAI_MEMORY;
+ case WSAHOST_NOT_FOUND: return EAI_NONAME;
+ case WSATYPE_NOT_FOUND: return EAI_SERVICE;
+ case WSAESOCKTNOSUPPORT: return EAI_SOCKTYPE;
+ default: return EAI_FAIL; /* Including WSANO_RECOVERY */
+ }
+}
+
+const char *win32_gai_strerror(int errcode) {
+ switch (errcode) {
+ case 0: errcode = 0; break;
+ case EAI_AGAIN: errcode = WSATRY_AGAIN; break;
+ case EAI_BADFLAGS: errcode = WSAEINVAL; break;
+ case EAI_FAMILY: errcode = WSAEAFNOSUPPORT; break;
+ case EAI_MEMORY: errcode = WSA_NOT_ENOUGH_MEMORY; break;
+ case EAI_NONAME: errcode = WSAHOST_NOT_FOUND; break;
+ case EAI_SERVICE: errcode = WSATYPE_NOT_FOUND; break;
+ case EAI_SOCKTYPE: errcode = WSAESOCKTNOSUPPORT; break;
+ default: errcode = WSANO_RECOVERY; break; /* Including EAI_FAIL */
+ }
+ return gai_strerror(errcode);
+}
+
+void win32_freeaddrinfo(struct addrinfo *res) {
+ freeaddrinfo(res);
+}
+
+SOCKET win32_socket(int domain, int type, int protocol) {
+ SOCKET s;
+
+ /* Note: This function is likely to be called before other functions, so run init here. */
+ if (!_initWinsock()) {
+ return INVALID_SOCKET;
+ }
+
+ _updateErrno((s = socket(domain, type, protocol)) != INVALID_SOCKET);
+ return s;
+}
+
+int win32_ioctl(SOCKET fd, unsigned long request, unsigned long *argp) {
+ int ret = ioctlsocket(fd, (long)request, argp);
+ _updateErrno(ret != SOCKET_ERROR);
+ return ret != SOCKET_ERROR ? ret : -1;
+}
+
+int win32_bind(SOCKET sockfd, const struct sockaddr *addr, socklen_t addrlen) {
+ int ret = bind(sockfd, addr, addrlen);
+ _updateErrno(ret != SOCKET_ERROR);
+ return ret != SOCKET_ERROR ? ret : -1;
+}
+
+int win32_connect(SOCKET sockfd, const struct sockaddr *addr, socklen_t addrlen) {
+ int ret = connect(sockfd, addr, addrlen);
+ _updateErrno(ret != SOCKET_ERROR);
+
+ /* For Winsock connect(), the WSAEWOULDBLOCK error means the same thing as
+ * EINPROGRESS for POSIX connect(), so we do that translation to keep POSIX
+ * logic consistent. */
+ if (errno == EWOULDBLOCK) {
+ errno = EINPROGRESS;
+ }
+
+ return ret != SOCKET_ERROR ? ret : -1;
+}
+
+int win32_getsockopt(SOCKET sockfd, int level, int optname, void *optval, socklen_t *optlen) {
+ int ret = 0;
+ if ((level == SOL_SOCKET) && ((optname == SO_RCVTIMEO) || (optname == SO_SNDTIMEO))) {
+ if (*optlen >= sizeof (struct timeval)) {
+ struct timeval *tv = optval;
+ DWORD timeout = 0;
+ socklen_t dwlen = 0;
+ ret = getsockopt(sockfd, level, optname, (char *)&timeout, &dwlen);
+ tv->tv_sec = timeout / 1000;
+ tv->tv_usec = (timeout * 1000) % 1000000;
+ } else {
+ ret = WSAEFAULT;
+ }
+ *optlen = sizeof (struct timeval);
+ } else {
+ ret = getsockopt(sockfd, level, optname, (char*)optval, optlen);
+ }
+ _updateErrno(ret != SOCKET_ERROR);
+ return ret != SOCKET_ERROR ? ret : -1;
+}
+
+int win32_setsockopt(SOCKET sockfd, int level, int optname, const void *optval, socklen_t optlen) {
+ int ret = 0;
+ if ((level == SOL_SOCKET) && ((optname == SO_RCVTIMEO) || (optname == SO_SNDTIMEO))) {
+ const struct timeval *tv = optval;
+ DWORD timeout = tv->tv_sec * 1000 + tv->tv_usec / 1000;
+ ret = setsockopt(sockfd, level, optname, (const char*)&timeout, sizeof(DWORD));
+ } else {
+ ret = setsockopt(sockfd, level, optname, (const char*)optval, optlen);
+ }
+ _updateErrno(ret != SOCKET_ERROR);
+ return ret != SOCKET_ERROR ? ret : -1;
+}
+
+int win32_close(SOCKET fd) {
+ int ret = closesocket(fd);
+ _updateErrno(ret != SOCKET_ERROR);
+ return ret != SOCKET_ERROR ? ret : -1;
+}
+
+ssize_t win32_recv(SOCKET sockfd, void *buf, size_t len, int flags) {
+ int ret = recv(sockfd, (char*)buf, (int)len, flags);
+ _updateErrno(ret != SOCKET_ERROR);
+ return ret != SOCKET_ERROR ? ret : -1;
+}
+
+ssize_t win32_send(SOCKET sockfd, const void *buf, size_t len, int flags) {
+ int ret = send(sockfd, (const char*)buf, (int)len, flags);
+ _updateErrno(ret != SOCKET_ERROR);
+ return ret != SOCKET_ERROR ? ret : -1;
+}
+
+int win32_poll(struct pollfd *fds, nfds_t nfds, int timeout) {
+ int ret = WSAPoll(fds, nfds, timeout);
+ _updateErrno(ret != SOCKET_ERROR);
+ return ret != SOCKET_ERROR ? ret : -1;
+}
+#endif /* _WIN32 */
diff --git a/deps/hiredis/sockcompat.h b/deps/hiredis/sockcompat.h
new file mode 100644
index 0000000..85810e8
--- /dev/null
+++ b/deps/hiredis/sockcompat.h
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) 2019, Marcus Geelnard <m at bitsnbites dot eu>
+ *
+ * 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.
+ */
+
+#ifndef __SOCKCOMPAT_H
+#define __SOCKCOMPAT_H
+
+#ifndef _WIN32
+/* For POSIX systems we use the standard BSD socket API. */
+#include <unistd.h>
+#include <sys/socket.h>
+#include <sys/select.h>
+#include <sys/un.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <poll.h>
+#else
+/* For Windows we use winsock. */
+#undef _WIN32_WINNT
+#define _WIN32_WINNT 0x0600 /* To get WSAPoll etc. */
+#include <winsock2.h>
+#include <ws2tcpip.h>
+#include <stddef.h>
+#include <errno.h>
+
+#ifdef _MSC_VER
+typedef long long ssize_t;
+#endif
+
+/* Emulate the parts of the BSD socket API that we need (override the winsock signatures). */
+int win32_getaddrinfo(const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res);
+const char *win32_gai_strerror(int errcode);
+void win32_freeaddrinfo(struct addrinfo *res);
+SOCKET win32_socket(int domain, int type, int protocol);
+int win32_ioctl(SOCKET fd, unsigned long request, unsigned long *argp);
+int win32_bind(SOCKET sockfd, const struct sockaddr *addr, socklen_t addrlen);
+int win32_connect(SOCKET sockfd, const struct sockaddr *addr, socklen_t addrlen);
+int win32_getsockopt(SOCKET sockfd, int level, int optname, void *optval, socklen_t *optlen);
+int win32_setsockopt(SOCKET sockfd, int level, int optname, const void *optval, socklen_t optlen);
+int win32_close(SOCKET fd);
+ssize_t win32_recv(SOCKET sockfd, void *buf, size_t len, int flags);
+ssize_t win32_send(SOCKET sockfd, const void *buf, size_t len, int flags);
+typedef ULONG nfds_t;
+int win32_poll(struct pollfd *fds, nfds_t nfds, int timeout);
+
+#ifndef REDIS_SOCKCOMPAT_IMPLEMENTATION
+#define getaddrinfo(node, service, hints, res) win32_getaddrinfo(node, service, hints, res)
+#undef gai_strerror
+#define gai_strerror(errcode) win32_gai_strerror(errcode)
+#define freeaddrinfo(res) win32_freeaddrinfo(res)
+#define socket(domain, type, protocol) win32_socket(domain, type, protocol)
+#define ioctl(fd, request, argp) win32_ioctl(fd, request, argp)
+#define bind(sockfd, addr, addrlen) win32_bind(sockfd, addr, addrlen)
+#define connect(sockfd, addr, addrlen) win32_connect(sockfd, addr, addrlen)
+#define getsockopt(sockfd, level, optname, optval, optlen) win32_getsockopt(sockfd, level, optname, optval, optlen)
+#define setsockopt(sockfd, level, optname, optval, optlen) win32_setsockopt(sockfd, level, optname, optval, optlen)
+#define close(fd) win32_close(fd)
+#define recv(sockfd, buf, len, flags) win32_recv(sockfd, buf, len, flags)
+#define send(sockfd, buf, len, flags) win32_send(sockfd, buf, len, flags)
+#define poll(fds, nfds, timeout) win32_poll(fds, nfds, timeout)
+#endif /* REDIS_SOCKCOMPAT_IMPLEMENTATION */
+#endif /* _WIN32 */
+
+#endif /* __SOCKCOMPAT_H */
diff --git a/deps/hiredis/ssl.c b/deps/hiredis/ssl.c
new file mode 100644
index 0000000..c10af7c
--- /dev/null
+++ b/deps/hiredis/ssl.c
@@ -0,0 +1,569 @@
+/*
+ * Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com>
+ * Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com>
+ * Copyright (c) 2019, Redis Labs
+ *
+ * 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 "hiredis.h"
+#include "async.h"
+
+#include <assert.h>
+#include <errno.h>
+#include <string.h>
+#ifdef _WIN32
+#include <windows.h>
+#include <wincrypt.h>
+#else
+#include <pthread.h>
+#endif
+
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+
+#include "win32.h"
+#include "async_private.h"
+#include "hiredis_ssl.h"
+
+void __redisSetError(redisContext *c, int type, const char *str);
+
+struct redisSSLContext {
+ /* Associated OpenSSL SSL_CTX as created by redisCreateSSLContext() */
+ SSL_CTX *ssl_ctx;
+
+ /* Requested SNI, or NULL */
+ char *server_name;
+};
+
+/* The SSL connection context is attached to SSL/TLS connections as a privdata. */
+typedef struct redisSSL {
+ /**
+ * OpenSSL SSL object.
+ */
+ SSL *ssl;
+
+ /**
+ * SSL_write() requires to be called again with the same arguments it was
+ * previously called with in the event of an SSL_read/SSL_write situation
+ */
+ size_t lastLen;
+
+ /** Whether the SSL layer requires read (possibly before a write) */
+ int wantRead;
+
+ /**
+ * Whether a write was requested prior to a read. If set, the write()
+ * should resume whenever a read takes place, if possible
+ */
+ int pendingWrite;
+} redisSSL;
+
+/* Forward declaration */
+redisContextFuncs redisContextSSLFuncs;
+
+/**
+ * OpenSSL global initialization and locking handling callbacks.
+ * Note that this is only required for OpenSSL < 1.1.0.
+ */
+
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+#define HIREDIS_USE_CRYPTO_LOCKS
+#endif
+
+#ifdef HIREDIS_USE_CRYPTO_LOCKS
+#ifdef _WIN32
+typedef CRITICAL_SECTION sslLockType;
+static void sslLockInit(sslLockType* l) {
+ InitializeCriticalSection(l);
+}
+static void sslLockAcquire(sslLockType* l) {
+ EnterCriticalSection(l);
+}
+static void sslLockRelease(sslLockType* l) {
+ LeaveCriticalSection(l);
+}
+#else
+typedef pthread_mutex_t sslLockType;
+static void sslLockInit(sslLockType *l) {
+ pthread_mutex_init(l, NULL);
+}
+static void sslLockAcquire(sslLockType *l) {
+ pthread_mutex_lock(l);
+}
+static void sslLockRelease(sslLockType *l) {
+ pthread_mutex_unlock(l);
+}
+#endif
+
+static sslLockType* ossl_locks;
+
+static void opensslDoLock(int mode, int lkid, const char *f, int line) {
+ sslLockType *l = ossl_locks + lkid;
+
+ if (mode & CRYPTO_LOCK) {
+ sslLockAcquire(l);
+ } else {
+ sslLockRelease(l);
+ }
+
+ (void)f;
+ (void)line;
+}
+
+static int initOpensslLocks(void) {
+ unsigned ii, nlocks;
+ if (CRYPTO_get_locking_callback() != NULL) {
+ /* Someone already set the callback before us. Don't destroy it! */
+ return REDIS_OK;
+ }
+ nlocks = CRYPTO_num_locks();
+ ossl_locks = hi_malloc(sizeof(*ossl_locks) * nlocks);
+ if (ossl_locks == NULL)
+ return REDIS_ERR;
+
+ for (ii = 0; ii < nlocks; ii++) {
+ sslLockInit(ossl_locks + ii);
+ }
+ CRYPTO_set_locking_callback(opensslDoLock);
+ return REDIS_OK;
+}
+#endif /* HIREDIS_USE_CRYPTO_LOCKS */
+
+int redisInitOpenSSL(void)
+{
+ SSL_library_init();
+#ifdef HIREDIS_USE_CRYPTO_LOCKS
+ initOpensslLocks();
+#endif
+
+ return REDIS_OK;
+}
+
+/**
+ * redisSSLContext helper context destruction.
+ */
+
+const char *redisSSLContextGetError(redisSSLContextError error)
+{
+ switch (error) {
+ case REDIS_SSL_CTX_NONE:
+ return "No Error";
+ case REDIS_SSL_CTX_CREATE_FAILED:
+ return "Failed to create OpenSSL SSL_CTX";
+ case REDIS_SSL_CTX_CERT_KEY_REQUIRED:
+ return "Client cert and key must both be specified or skipped";
+ case REDIS_SSL_CTX_CA_CERT_LOAD_FAILED:
+ return "Failed to load CA Certificate or CA Path";
+ case REDIS_SSL_CTX_CLIENT_CERT_LOAD_FAILED:
+ 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 certificate 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";
+ }
+}
+
+void redisFreeSSLContext(redisSSLContext *ctx)
+{
+ if (!ctx)
+ return;
+
+ if (ctx->server_name) {
+ hi_free(ctx->server_name);
+ ctx->server_name = NULL;
+ }
+
+ if (ctx->ssl_ctx) {
+ SSL_CTX_free(ctx->ssl_ctx);
+ ctx->ssl_ctx = NULL;
+ }
+
+ hi_free(ctx);
+}
+
+
+/**
+ * redisSSLContext helper context initialization.
+ */
+
+redisSSLContext *redisCreateSSLContext(const char *cacert_filename, const char *capath,
+ 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;
+
+ ctx->ssl_ctx = SSL_CTX_new(SSLv23_client_method());
+ if (!ctx->ssl_ctx) {
+ if (error) *error = REDIS_SSL_CTX_CREATE_FAILED;
+ goto error;
+ }
+
+ SSL_CTX_set_options(ctx->ssl_ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);
+ SSL_CTX_set_verify(ctx->ssl_ctx, SSL_VERIFY_PEER, NULL);
+
+ if ((cert_filename != NULL && private_key_filename == NULL) ||
+ (private_key_filename != NULL && cert_filename == NULL)) {
+ if (error) *error = REDIS_SSL_CTX_CERT_KEY_REQUIRED;
+ goto error;
+ }
+
+ if (capath || cacert_filename) {
+#ifdef _WIN32
+ if (0 == strcmp(cacert_filename, "wincert")) {
+ win_store = CertOpenSystemStore(NULL, "Root");
+ if (!win_store) {
+ if (error) *error = 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;
+ }
+ }
+
+ if (cert_filename) {
+ if (!SSL_CTX_use_certificate_chain_file(ctx->ssl_ctx, cert_filename)) {
+ if (error) *error = REDIS_SSL_CTX_CLIENT_CERT_LOAD_FAILED;
+ goto error;
+ }
+ if (!SSL_CTX_use_PrivateKey_file(ctx->ssl_ctx, private_key_filename, SSL_FILETYPE_PEM)) {
+ if (error) *error = REDIS_SSL_CTX_PRIVATE_KEY_LOAD_FAILED;
+ goto error;
+ }
+ }
+
+ if (server_name)
+ ctx->server_name = hi_strdup(server_name);
+
+ return ctx;
+
+error:
+#ifdef _WIN32
+ CertFreeCertificateContext(win_ctx);
+ CertCloseStore(win_store, 0);
+#endif
+ redisFreeSSLContext(ctx);
+ return NULL;
+}
+
+/**
+ * SSL Connection initialization.
+ */
+
+
+static int redisSSLConnect(redisContext *c, SSL *ssl) {
+ if (c->privctx) {
+ __redisSetError(c, REDIS_ERR_OTHER, "redisContext was already associated");
+ return REDIS_ERR;
+ }
+
+ redisSSL *rssl = hi_calloc(1, sizeof(redisSSL));
+ if (rssl == NULL) {
+ __redisSetError(c, REDIS_ERR_OOM, "Out of memory");
+ return REDIS_ERR;
+ }
+
+ c->funcs = &redisContextSSLFuncs;
+ rssl->ssl = ssl;
+
+ SSL_set_mode(rssl->ssl, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
+ SSL_set_fd(rssl->ssl, c->fd);
+ SSL_set_connect_state(rssl->ssl);
+
+ ERR_clear_error();
+ int rv = SSL_connect(rssl->ssl);
+ if (rv == 1) {
+ c->privctx = rssl;
+ return REDIS_OK;
+ }
+
+ rv = SSL_get_error(rssl->ssl, rv);
+ if (((c->flags & REDIS_BLOCK) == 0) &&
+ (rv == SSL_ERROR_WANT_READ || rv == SSL_ERROR_WANT_WRITE)) {
+ c->privctx = rssl;
+ return REDIS_OK;
+ }
+
+ if (c->err == 0) {
+ char err[512];
+ if (rv == SSL_ERROR_SYSCALL)
+ snprintf(err,sizeof(err)-1,"SSL_connect failed: %s",strerror(errno));
+ else {
+ unsigned long e = ERR_peek_last_error();
+ snprintf(err,sizeof(err)-1,"SSL_connect failed: %s",
+ ERR_reason_error_string(e));
+ }
+ __redisSetError(c, REDIS_ERR_IO, err);
+ }
+
+ hi_free(rssl);
+ return REDIS_ERR;
+}
+
+/**
+ * A wrapper around redisSSLConnect() for users who manage their own context and
+ * create their own SSL object.
+ */
+
+int redisInitiateSSL(redisContext *c, SSL *ssl) {
+ return redisSSLConnect(c, ssl);
+}
+
+/**
+ * A wrapper around redisSSLConnect() for users who use redisSSLContext and don't
+ * manage their own SSL objects.
+ */
+
+int redisInitiateSSLWithContext(redisContext *c, redisSSLContext *redis_ssl_ctx)
+{
+ if (!c || !redis_ssl_ctx)
+ return REDIS_ERR;
+
+ /* We want to verify that redisSSLConnect() won't fail on this, as it will
+ * not own the SSL object in that case and we'll end up leaking.
+ */
+ if (c->privctx)
+ return REDIS_ERR;
+
+ SSL *ssl = SSL_new(redis_ssl_ctx->ssl_ctx);
+ if (!ssl) {
+ __redisSetError(c, REDIS_ERR_OTHER, "Couldn't create new SSL instance");
+ goto error;
+ }
+
+ if (redis_ssl_ctx->server_name) {
+ if (!SSL_set_tlsext_host_name(ssl, redis_ssl_ctx->server_name)) {
+ __redisSetError(c, REDIS_ERR_OTHER, "Failed to set server_name/SNI");
+ goto error;
+ }
+ }
+
+ if (redisSSLConnect(c, ssl) != REDIS_OK) {
+ goto error;
+ }
+
+ return REDIS_OK;
+
+error:
+ if (ssl)
+ SSL_free(ssl);
+ return REDIS_ERR;
+}
+
+static int maybeCheckWant(redisSSL *rssl, int rv) {
+ /**
+ * If the error is WANT_READ or WANT_WRITE, the appropriate flags are set
+ * and true is returned. False is returned otherwise
+ */
+ if (rv == SSL_ERROR_WANT_READ) {
+ rssl->wantRead = 1;
+ return 1;
+ } else if (rv == SSL_ERROR_WANT_WRITE) {
+ rssl->pendingWrite = 1;
+ return 1;
+ } else {
+ return 0;
+ }
+}
+
+/**
+ * Implementation of redisContextFuncs for SSL connections.
+ */
+
+static void redisSSLFree(void *privctx){
+ redisSSL *rsc = privctx;
+
+ if (!rsc) return;
+ if (rsc->ssl) {
+ SSL_free(rsc->ssl);
+ rsc->ssl = NULL;
+ }
+ hi_free(rsc);
+}
+
+static ssize_t redisSSLRead(redisContext *c, char *buf, size_t bufcap) {
+ redisSSL *rssl = c->privctx;
+
+ int nread = SSL_read(rssl->ssl, buf, bufcap);
+ if (nread > 0) {
+ return nread;
+ } else if (nread == 0) {
+ __redisSetError(c, REDIS_ERR_EOF, "Server closed the connection");
+ return -1;
+ } else {
+ int err = SSL_get_error(rssl->ssl, nread);
+ if (c->flags & REDIS_BLOCK) {
+ /**
+ * In blocking mode, we should never end up in a situation where
+ * we get an error without it being an actual error, except
+ * in the case of EINTR, which can be spuriously received from
+ * debuggers or whatever.
+ */
+ if (errno == EINTR) {
+ return 0;
+ } else {
+ const char *msg = NULL;
+ if (errno == EAGAIN) {
+ msg = "Resource temporarily unavailable";
+ }
+ __redisSetError(c, REDIS_ERR_IO, msg);
+ return -1;
+ }
+ }
+
+ /**
+ * We can very well get an EWOULDBLOCK/EAGAIN, however
+ */
+ if (maybeCheckWant(rssl, err)) {
+ return 0;
+ } else {
+ __redisSetError(c, REDIS_ERR_IO, NULL);
+ return -1;
+ }
+ }
+}
+
+static ssize_t redisSSLWrite(redisContext *c) {
+ redisSSL *rssl = c->privctx;
+
+ size_t len = rssl->lastLen ? rssl->lastLen : hi_sdslen(c->obuf);
+ int rv = SSL_write(rssl->ssl, c->obuf, len);
+
+ if (rv > 0) {
+ rssl->lastLen = 0;
+ } else if (rv < 0) {
+ rssl->lastLen = len;
+
+ int err = SSL_get_error(rssl->ssl, rv);
+ if ((c->flags & REDIS_BLOCK) == 0 && maybeCheckWant(rssl, err)) {
+ return 0;
+ } else {
+ __redisSetError(c, REDIS_ERR_IO, NULL);
+ return -1;
+ }
+ }
+ return rv;
+}
+
+static void redisSSLAsyncRead(redisAsyncContext *ac) {
+ int rv;
+ redisSSL *rssl = ac->c.privctx;
+ redisContext *c = &ac->c;
+
+ rssl->wantRead = 0;
+
+ if (rssl->pendingWrite) {
+ int done;
+
+ /* This is probably just a write event */
+ rssl->pendingWrite = 0;
+ rv = redisBufferWrite(c, &done);
+ if (rv == REDIS_ERR) {
+ __redisAsyncDisconnect(ac);
+ return;
+ } else if (!done) {
+ _EL_ADD_WRITE(ac);
+ }
+ }
+
+ rv = redisBufferRead(c);
+ if (rv == REDIS_ERR) {
+ __redisAsyncDisconnect(ac);
+ } else {
+ _EL_ADD_READ(ac);
+ redisProcessCallbacks(ac);
+ }
+}
+
+static void redisSSLAsyncWrite(redisAsyncContext *ac) {
+ int rv, done = 0;
+ redisSSL *rssl = ac->c.privctx;
+ redisContext *c = &ac->c;
+
+ rssl->pendingWrite = 0;
+ rv = redisBufferWrite(c, &done);
+ if (rv == REDIS_ERR) {
+ __redisAsyncDisconnect(ac);
+ return;
+ }
+
+ if (!done) {
+ if (rssl->wantRead) {
+ /* Need to read-before-write */
+ rssl->pendingWrite = 1;
+ _EL_DEL_WRITE(ac);
+ } else {
+ /* No extra reads needed, just need to write more */
+ _EL_ADD_WRITE(ac);
+ }
+ } else {
+ /* Already done! */
+ _EL_DEL_WRITE(ac);
+ }
+
+ /* Always reschedule a read */
+ _EL_ADD_READ(ac);
+}
+
+redisContextFuncs redisContextSSLFuncs = {
+ .free_privctx = redisSSLFree,
+ .async_read = redisSSLAsyncRead,
+ .async_write = redisSSLAsyncWrite,
+ .read = redisSSLRead,
+ .write = redisSSLWrite
+};
+
diff --git a/deps/hiredis/test.c b/deps/hiredis/test.c
new file mode 100644
index 0000000..306aa55
--- /dev/null
+++ b/deps/hiredis/test.c
@@ -0,0 +1,2041 @@
+#include "fmacros.h"
+#include "sockcompat.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#ifndef _WIN32
+#include <strings.h>
+#include <sys/time.h>
+#endif
+#include <assert.h>
+#include <signal.h>
+#include <errno.h>
+#include <limits.h>
+#include <math.h>
+
+#include "hiredis.h"
+#include "async.h"
+#ifdef HIREDIS_TEST_SSL
+#include "hiredis_ssl.h"
+#endif
+#ifdef HIREDIS_TEST_ASYNC
+#include "adapters/libevent.h"
+#include <event2/event.h>
+#endif
+#include "net.h"
+#include "win32.h"
+
+enum connection_type {
+ CONN_TCP,
+ CONN_UNIX,
+ CONN_FD,
+ CONN_SSL
+};
+
+struct config {
+ enum connection_type type;
+
+ struct {
+ const char *host;
+ int port;
+ struct timeval timeout;
+ } tcp;
+
+ struct {
+ const char *path;
+ } unix_sock;
+
+ struct {
+ const char *host;
+ int port;
+ const char *ca_cert;
+ const char *cert;
+ const char *key;
+ } ssl;
+};
+
+struct privdata {
+ int dtor_counter;
+};
+
+struct pushCounters {
+ int nil;
+ int str;
+};
+
+static int insecure_calloc_calls;
+
+#ifdef HIREDIS_TEST_SSL
+redisSSLContext *_ssl_ctx = NULL;
+#endif
+
+/* The following lines make up our testing "framework" :) */
+static int tests = 0, fails = 0, skips = 0;
+#define test(_s) { printf("#%02d ", ++tests); printf(_s); }
+#define test_cond(_c) if(_c) printf("\033[0;32mPASSED\033[0;0m\n"); else {printf("\033[0;31mFAILED\033[0;0m\n"); fails++;}
+#define test_skipped() { printf("\033[01;33mSKIPPED\033[0;0m\n"); skips++; }
+
+static long long usec(void) {
+#ifndef _MSC_VER
+ struct timeval tv;
+ gettimeofday(&tv,NULL);
+ return (((long long)tv.tv_sec)*1000000)+tv.tv_usec;
+#else
+ FILETIME ft;
+ GetSystemTimeAsFileTime(&ft);
+ return (((long long)ft.dwHighDateTime << 32) | ft.dwLowDateTime) / 10;
+#endif
+}
+
+/* The assert() calls below have side effects, so we need assert()
+ * even if we are compiling without asserts (-DNDEBUG). */
+#ifdef NDEBUG
+#undef assert
+#define assert(e) (void)(e)
+#endif
+
+/* Helper to extract Redis version information. Aborts on any failure. */
+#define REDIS_VERSION_FIELD "redis_version:"
+void get_redis_version(redisContext *c, int *majorptr, int *minorptr) {
+ redisReply *reply;
+ char *eptr, *s, *e;
+ int major, minor;
+
+ reply = redisCommand(c, "INFO");
+ if (reply == NULL || c->err || reply->type != REDIS_REPLY_STRING)
+ goto abort;
+ if ((s = strstr(reply->str, REDIS_VERSION_FIELD)) == NULL)
+ goto abort;
+
+ s += strlen(REDIS_VERSION_FIELD);
+
+ /* We need a field terminator and at least 'x.y.z' (5) bytes of data */
+ if ((e = strstr(s, "\r\n")) == NULL || (e - s) < 5)
+ goto abort;
+
+ /* Extract version info */
+ major = strtol(s, &eptr, 10);
+ if (*eptr != '.') goto abort;
+ minor = strtol(eptr+1, NULL, 10);
+
+ /* Push info the caller wants */
+ if (majorptr) *majorptr = major;
+ if (minorptr) *minorptr = minor;
+
+ freeReplyObject(reply);
+ return;
+
+abort:
+ freeReplyObject(reply);
+ fprintf(stderr, "Error: Cannot determine Redis version, aborting\n");
+ exit(1);
+}
+
+static redisContext *select_database(redisContext *c) {
+ redisReply *reply;
+
+ /* Switch to DB 9 for testing, now that we know we can chat. */
+ reply = redisCommand(c,"SELECT 9");
+ assert(reply != NULL);
+ freeReplyObject(reply);
+
+ /* Make sure the DB is emtpy */
+ reply = redisCommand(c,"DBSIZE");
+ assert(reply != NULL);
+ if (reply->type == REDIS_REPLY_INTEGER && reply->integer == 0) {
+ /* Awesome, DB 9 is empty and we can continue. */
+ freeReplyObject(reply);
+ } else {
+ printf("Database #9 is not empty, test can not continue\n");
+ exit(1);
+ }
+
+ return c;
+}
+
+/* Switch protocol */
+static void send_hello(redisContext *c, int version) {
+ redisReply *reply;
+ int expected;
+
+ reply = redisCommand(c, "HELLO %d", version);
+ expected = version == 3 ? REDIS_REPLY_MAP : REDIS_REPLY_ARRAY;
+ assert(reply != NULL && reply->type == expected);
+ freeReplyObject(reply);
+}
+
+/* Togggle client tracking */
+static void send_client_tracking(redisContext *c, const char *str) {
+ redisReply *reply;
+
+ reply = redisCommand(c, "CLIENT TRACKING %s", str);
+ assert(reply != NULL && reply->type == REDIS_REPLY_STATUS);
+ freeReplyObject(reply);
+}
+
+static int disconnect(redisContext *c, int keep_fd) {
+ redisReply *reply;
+
+ /* Make sure we're on DB 9. */
+ reply = redisCommand(c,"SELECT 9");
+ assert(reply != NULL);
+ freeReplyObject(reply);
+ reply = redisCommand(c,"FLUSHDB");
+ assert(reply != NULL);
+ freeReplyObject(reply);
+
+ /* Free the context as well, but keep the fd if requested. */
+ if (keep_fd)
+ return redisFreeKeepFd(c);
+ redisFree(c);
+ return -1;
+}
+
+static void do_ssl_handshake(redisContext *c) {
+#ifdef HIREDIS_TEST_SSL
+ redisInitiateSSLWithContext(c, _ssl_ctx);
+ if (c->err) {
+ printf("SSL error: %s\n", c->errstr);
+ redisFree(c);
+ exit(1);
+ }
+#else
+ (void) c;
+#endif
+}
+
+static redisContext *do_connect(struct config config) {
+ redisContext *c = NULL;
+
+ if (config.type == CONN_TCP) {
+ c = redisConnect(config.tcp.host, config.tcp.port);
+ } else if (config.type == CONN_SSL) {
+ c = redisConnect(config.ssl.host, config.ssl.port);
+ } else if (config.type == CONN_UNIX) {
+ c = redisConnectUnix(config.unix_sock.path);
+ } else if (config.type == CONN_FD) {
+ /* Create a dummy connection just to get an fd to inherit */
+ redisContext *dummy_ctx = redisConnectUnix(config.unix_sock.path);
+ if (dummy_ctx) {
+ int fd = disconnect(dummy_ctx, 1);
+ printf("Connecting to inherited fd %d\n", fd);
+ c = redisConnectFd(fd);
+ }
+ } else {
+ assert(NULL);
+ }
+
+ if (c == NULL) {
+ printf("Connection error: can't allocate redis context\n");
+ exit(1);
+ } else if (c->err) {
+ printf("Connection error: %s\n", c->errstr);
+ redisFree(c);
+ exit(1);
+ }
+
+ if (config.type == CONN_SSL) {
+ do_ssl_handshake(c);
+ }
+
+ return select_database(c);
+}
+
+static void do_reconnect(redisContext *c, struct config config) {
+ redisReconnect(c);
+
+ if (config.type == CONN_SSL) {
+ do_ssl_handshake(c);
+ }
+}
+
+static void test_format_commands(void) {
+ char *cmd;
+ int len;
+
+ test("Format command without interpolation: ");
+ len = redisFormatCommand(&cmd,"SET foo bar");
+ test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 &&
+ len == 4+4+(3+2)+4+(3+2)+4+(3+2));
+ hi_free(cmd);
+
+ test("Format command with %%s string interpolation: ");
+ len = redisFormatCommand(&cmd,"SET %s %s","foo","bar");
+ test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 &&
+ len == 4+4+(3+2)+4+(3+2)+4+(3+2));
+ hi_free(cmd);
+
+ test("Format command with %%s and an empty string: ");
+ len = redisFormatCommand(&cmd,"SET %s %s","foo","");
+ test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$0\r\n\r\n",len) == 0 &&
+ len == 4+4+(3+2)+4+(3+2)+4+(0+2));
+ hi_free(cmd);
+
+ test("Format command with an empty string in between proper interpolations: ");
+ len = redisFormatCommand(&cmd,"SET %s %s","","foo");
+ test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$0\r\n\r\n$3\r\nfoo\r\n",len) == 0 &&
+ len == 4+4+(3+2)+4+(0+2)+4+(3+2));
+ hi_free(cmd);
+
+ test("Format command with %%b string interpolation: ");
+ len = redisFormatCommand(&cmd,"SET %b %b","foo",(size_t)3,"b\0r",(size_t)3);
+ test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nb\0r\r\n",len) == 0 &&
+ len == 4+4+(3+2)+4+(3+2)+4+(3+2));
+ hi_free(cmd);
+
+ test("Format command with %%b and an empty string: ");
+ len = redisFormatCommand(&cmd,"SET %b %b","foo",(size_t)3,"",(size_t)0);
+ test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$0\r\n\r\n",len) == 0 &&
+ len == 4+4+(3+2)+4+(3+2)+4+(0+2));
+ hi_free(cmd);
+
+ test("Format command with literal %%: ");
+ len = redisFormatCommand(&cmd,"SET %% %%");
+ test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$1\r\n%\r\n$1\r\n%\r\n",len) == 0 &&
+ len == 4+4+(3+2)+4+(1+2)+4+(1+2));
+ hi_free(cmd);
+
+ /* Vararg width depends on the type. These tests make sure that the
+ * width is correctly determined using the format and subsequent varargs
+ * can correctly be interpolated. */
+#define INTEGER_WIDTH_TEST(fmt, type) do { \
+ type value = 123; \
+ test("Format command with printf-delegation (" #type "): "); \
+ len = redisFormatCommand(&cmd,"key:%08" fmt " str:%s", value, "hello"); \
+ test_cond(strncmp(cmd,"*2\r\n$12\r\nkey:00000123\r\n$9\r\nstr:hello\r\n",len) == 0 && \
+ len == 4+5+(12+2)+4+(9+2)); \
+ hi_free(cmd); \
+} while(0)
+
+#define FLOAT_WIDTH_TEST(type) do { \
+ type value = 123.0; \
+ test("Format command with printf-delegation (" #type "): "); \
+ len = redisFormatCommand(&cmd,"key:%08.3f str:%s", value, "hello"); \
+ test_cond(strncmp(cmd,"*2\r\n$12\r\nkey:0123.000\r\n$9\r\nstr:hello\r\n",len) == 0 && \
+ len == 4+5+(12+2)+4+(9+2)); \
+ hi_free(cmd); \
+} while(0)
+
+ INTEGER_WIDTH_TEST("d", int);
+ INTEGER_WIDTH_TEST("hhd", char);
+ INTEGER_WIDTH_TEST("hd", short);
+ INTEGER_WIDTH_TEST("ld", long);
+ INTEGER_WIDTH_TEST("lld", long long);
+ INTEGER_WIDTH_TEST("u", unsigned int);
+ INTEGER_WIDTH_TEST("hhu", unsigned char);
+ INTEGER_WIDTH_TEST("hu", unsigned short);
+ INTEGER_WIDTH_TEST("lu", unsigned long);
+ INTEGER_WIDTH_TEST("llu", unsigned long long);
+ FLOAT_WIDTH_TEST(float);
+ FLOAT_WIDTH_TEST(double);
+
+ test("Format command with invalid printf format: ");
+ len = redisFormatCommand(&cmd,"key:%08p %b",(void*)1234,"foo",(size_t)3);
+ test_cond(len == -1);
+
+ const char *argv[3];
+ argv[0] = "SET";
+ argv[1] = "foo\0xxx";
+ argv[2] = "bar";
+ size_t lens[3] = { 3, 7, 3 };
+ int argc = 3;
+
+ test("Format command by passing argc/argv without lengths: ");
+ len = redisFormatCommandArgv(&cmd,argc,argv,NULL);
+ test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 &&
+ len == 4+4+(3+2)+4+(3+2)+4+(3+2));
+ hi_free(cmd);
+
+ test("Format command by passing argc/argv with lengths: ");
+ len = redisFormatCommandArgv(&cmd,argc,argv,lens);
+ test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$7\r\nfoo\0xxx\r\n$3\r\nbar\r\n",len) == 0 &&
+ len == 4+4+(3+2)+4+(7+2)+4+(3+2));
+ hi_free(cmd);
+
+ hisds sds_cmd;
+
+ sds_cmd = NULL;
+ test("Format command into hisds 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);
+
+ sds_cmd = NULL;
+ test("Format command into hisds 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);
+}
+
+static void test_append_formatted_commands(struct config config) {
+ redisContext *c;
+ redisReply *reply;
+ char *cmd;
+ int len;
+
+ c = do_connect(config);
+
+ test("Append format command: ");
+
+ len = redisFormatCommand(&cmd, "SET foo bar");
+
+ test_cond(redisAppendFormattedCommand(c, cmd, len) == REDIS_OK);
+
+ assert(redisGetReply(c, (void*)&reply) == REDIS_OK);
+
+ hi_free(cmd);
+ freeReplyObject(reply);
+
+ disconnect(c, 0);
+}
+
+static void test_reply_reader(void) {
+ redisReader *reader;
+ void *reply, *root;
+ int ret;
+ int i;
+
+ test("Error handling in reply parser: ");
+ reader = redisReaderCreate();
+ redisReaderFeed(reader,(char*)"@foo\r\n",6);
+ ret = redisReaderGetReply(reader,NULL);
+ test_cond(ret == REDIS_ERR &&
+ strcasecmp(reader->errstr,"Protocol error, got \"@\" as reply type byte") == 0);
+ redisReaderFree(reader);
+
+ /* when the reply already contains multiple items, they must be free'd
+ * on an error. valgrind will bark when this doesn't happen. */
+ test("Memory cleanup in reply parser: ");
+ reader = redisReaderCreate();
+ redisReaderFeed(reader,(char*)"*2\r\n",4);
+ redisReaderFeed(reader,(char*)"$5\r\nhello\r\n",11);
+ redisReaderFeed(reader,(char*)"@foo\r\n",6);
+ ret = redisReaderGetReply(reader,NULL);
+ test_cond(ret == REDIS_ERR &&
+ strcasecmp(reader->errstr,"Protocol error, got \"@\" as reply type byte") == 0);
+ redisReaderFree(reader);
+
+ reader = redisReaderCreate();
+ test("Can handle arbitrarily nested multi-bulks: ");
+ for (i = 0; i < 128; i++) {
+ redisReaderFeed(reader,(char*)"*1\r\n", 4);
+ }
+ redisReaderFeed(reader,(char*)"$6\r\nLOLWUT\r\n",12);
+ ret = redisReaderGetReply(reader,&reply);
+ root = reply; /* Keep track of the root reply */
+ test_cond(ret == REDIS_OK &&
+ ((redisReply*)reply)->type == REDIS_REPLY_ARRAY &&
+ ((redisReply*)reply)->elements == 1);
+
+ test("Can parse arbitrarily nested multi-bulks correctly: ");
+ while(i--) {
+ assert(reply != NULL && ((redisReply*)reply)->type == REDIS_REPLY_ARRAY);
+ reply = ((redisReply*)reply)->element[0];
+ }
+ test_cond(((redisReply*)reply)->type == REDIS_REPLY_STRING &&
+ !memcmp(((redisReply*)reply)->str, "LOLWUT", 6));
+ freeReplyObject(root);
+ redisReaderFree(reader);
+
+ test("Correctly parses LLONG_MAX: ");
+ reader = redisReaderCreate();
+ redisReaderFeed(reader, ":9223372036854775807\r\n",22);
+ ret = redisReaderGetReply(reader,&reply);
+ test_cond(ret == REDIS_OK &&
+ ((redisReply*)reply)->type == REDIS_REPLY_INTEGER &&
+ ((redisReply*)reply)->integer == LLONG_MAX);
+ freeReplyObject(reply);
+ redisReaderFree(reader);
+
+ test("Set error when > LLONG_MAX: ");
+ reader = redisReaderCreate();
+ redisReaderFeed(reader, ":9223372036854775808\r\n",22);
+ ret = redisReaderGetReply(reader,&reply);
+ test_cond(ret == REDIS_ERR &&
+ strcasecmp(reader->errstr,"Bad integer value") == 0);
+ freeReplyObject(reply);
+ redisReaderFree(reader);
+
+ test("Correctly parses LLONG_MIN: ");
+ reader = redisReaderCreate();
+ redisReaderFeed(reader, ":-9223372036854775808\r\n",23);
+ ret = redisReaderGetReply(reader,&reply);
+ test_cond(ret == REDIS_OK &&
+ ((redisReply*)reply)->type == REDIS_REPLY_INTEGER &&
+ ((redisReply*)reply)->integer == LLONG_MIN);
+ freeReplyObject(reply);
+ redisReaderFree(reader);
+
+ test("Set error when < LLONG_MIN: ");
+ reader = redisReaderCreate();
+ redisReaderFeed(reader, ":-9223372036854775809\r\n",23);
+ ret = redisReaderGetReply(reader,&reply);
+ test_cond(ret == REDIS_ERR &&
+ strcasecmp(reader->errstr,"Bad integer value") == 0);
+ freeReplyObject(reply);
+ redisReaderFree(reader);
+
+ test("Set error when array < -1: ");
+ reader = redisReaderCreate();
+ redisReaderFeed(reader, "*-2\r\n+asdf\r\n",12);
+ ret = redisReaderGetReply(reader,&reply);
+ test_cond(ret == REDIS_ERR &&
+ strcasecmp(reader->errstr,"Multi-bulk length out of range") == 0);
+ freeReplyObject(reply);
+ redisReaderFree(reader);
+
+ test("Set error when bulk < -1: ");
+ reader = redisReaderCreate();
+ redisReaderFeed(reader, "$-2\r\nasdf\r\n",11);
+ ret = redisReaderGetReply(reader,&reply);
+ test_cond(ret == REDIS_ERR &&
+ strcasecmp(reader->errstr,"Bulk string length out of range") == 0);
+ freeReplyObject(reply);
+ redisReaderFree(reader);
+
+ test("Can configure maximum multi-bulk elements: ");
+ reader = redisReaderCreate();
+ reader->maxelements = 1024;
+ redisReaderFeed(reader, "*1025\r\n", 7);
+ ret = redisReaderGetReply(reader,&reply);
+ test_cond(ret == REDIS_ERR &&
+ strcasecmp(reader->errstr, "Multi-bulk length out of range") == 0);
+ 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();
+ redisReaderFeed(reader, "*9223372036854775807\r\n+asdf\r\n",29);
+ ret = redisReaderGetReply(reader,&reply);
+ test_cond(ret == REDIS_ERR &&
+ strcasecmp(reader->errstr,"Multi-bulk length out of range") == 0);
+ freeReplyObject(reply);
+ redisReaderFree(reader);
+
+ test("Set error when bulk > SIZE_MAX: ");
+ reader = redisReaderCreate();
+ redisReaderFeed(reader, "$9223372036854775807\r\nasdf\r\n",28);
+ ret = redisReaderGetReply(reader,&reply);
+ test_cond(ret == REDIS_ERR &&
+ strcasecmp(reader->errstr,"Bulk string length out of range") == 0);
+ freeReplyObject(reply);
+ redisReaderFree(reader);
+#endif
+
+ test("Works with NULL functions for reply: ");
+ reader = redisReaderCreate();
+ reader->fn = NULL;
+ redisReaderFeed(reader,(char*)"+OK\r\n",5);
+ ret = redisReaderGetReply(reader,&reply);
+ test_cond(ret == REDIS_OK && reply == (void*)REDIS_REPLY_STATUS);
+ redisReaderFree(reader);
+
+ test("Works when a single newline (\\r\\n) covers two calls to feed: ");
+ reader = redisReaderCreate();
+ reader->fn = NULL;
+ redisReaderFeed(reader,(char*)"+OK\r",4);
+ ret = redisReaderGetReply(reader,&reply);
+ assert(ret == REDIS_OK && reply == NULL);
+ redisReaderFeed(reader,(char*)"\n",1);
+ ret = redisReaderGetReply(reader,&reply);
+ test_cond(ret == REDIS_OK && reply == (void*)REDIS_REPLY_STATUS);
+ redisReaderFree(reader);
+
+ test("Don't reset state after protocol error: ");
+ reader = redisReaderCreate();
+ reader->fn = NULL;
+ redisReaderFeed(reader,(char*)"x",1);
+ ret = redisReaderGetReply(reader,&reply);
+ assert(ret == REDIS_ERR);
+ ret = redisReaderGetReply(reader,&reply);
+ test_cond(ret == REDIS_ERR && reply == NULL);
+ redisReaderFree(reader);
+
+ /* Regression test for issue #45 on GitHub. */
+ test("Don't do empty allocation for empty multi bulk: ");
+ reader = redisReaderCreate();
+ redisReaderFeed(reader,(char*)"*0\r\n",4);
+ ret = redisReaderGetReply(reader,&reply);
+ test_cond(ret == REDIS_OK &&
+ ((redisReply*)reply)->type == REDIS_REPLY_ARRAY &&
+ ((redisReply*)reply)->elements == 0);
+ freeReplyObject(reply);
+ redisReaderFree(reader);
+
+ /* RESP3 verbatim strings (GitHub issue #802) */
+ test("Can parse RESP3 verbatim strings: ");
+ reader = redisReaderCreate();
+ redisReaderFeed(reader,(char*)"=10\r\ntxt:LOLWUT\r\n",17);
+ ret = redisReaderGetReply(reader,&reply);
+ test_cond(ret == REDIS_OK &&
+ ((redisReply*)reply)->type == REDIS_REPLY_VERB &&
+ !memcmp(((redisReply*)reply)->str,"LOLWUT", 6));
+ freeReplyObject(reply);
+ redisReaderFree(reader);
+
+ /* RESP3 push messages (Github issue #815) */
+ test("Can parse RESP3 push messages: ");
+ reader = redisReaderCreate();
+ redisReaderFeed(reader,(char*)">2\r\n$6\r\nLOLWUT\r\n:42\r\n",21);
+ ret = redisReaderGetReply(reader,&reply);
+ test_cond(ret == REDIS_OK &&
+ ((redisReply*)reply)->type == REDIS_REPLY_PUSH &&
+ ((redisReply*)reply)->elements == 2 &&
+ ((redisReply*)reply)->element[0]->type == REDIS_REPLY_STRING &&
+ !memcmp(((redisReply*)reply)->element[0]->str,"LOLWUT",6) &&
+ ((redisReply*)reply)->element[1]->type == REDIS_REPLY_INTEGER &&
+ ((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) {
+ void *redisCtx = NULL;
+ void *reply = NULL;
+
+ test("Don't fail when redisFree is passed a NULL value: ");
+ redisFree(redisCtx);
+ test_cond(redisCtx == NULL);
+
+ test("Don't fail when freeReplyObject is passed a NULL value: ");
+ freeReplyObject(reply);
+ test_cond(reply == NULL);
+}
+
+static void *hi_malloc_fail(size_t size) {
+ (void)size;
+ return NULL;
+}
+
+static void *hi_calloc_fail(size_t nmemb, size_t size) {
+ (void)nmemb;
+ (void)size;
+ return NULL;
+}
+
+static void *hi_calloc_insecure(size_t nmemb, size_t size) {
+ (void)nmemb;
+ (void)size;
+ insecure_calloc_calls++;
+ return (void*)0xdeadc0de;
+}
+
+static void *hi_realloc_fail(void *ptr, size_t size) {
+ (void)ptr;
+ (void)size;
+ return NULL;
+}
+
+static void test_allocator_injection(void) {
+ void *ptr;
+
+ hiredisAllocFuncs ha = {
+ .mallocFn = hi_malloc_fail,
+ .callocFn = hi_calloc_fail,
+ .reallocFn = hi_realloc_fail,
+ .strdupFn = strdup,
+ .freeFn = free,
+ };
+
+ // Override hiredis allocators
+ hiredisSetAllocators(&ha);
+
+ test("redisContext uses injected allocators: ");
+ redisContext *c = redisConnect("localhost", 6379);
+ test_cond(c == NULL);
+
+ test("redisReader uses injected allocators: ");
+ 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();
+}
+
+#define HIREDIS_BAD_DOMAIN "idontexist-noreally.com"
+static void test_blocking_connection_errors(void) {
+ redisContext *c;
+ struct addrinfo hints = {.ai_family = AF_INET};
+ struct addrinfo *ai_tmp = NULL;
+
+ int rv = getaddrinfo(HIREDIS_BAD_DOMAIN, "6379", &hints, &ai_tmp);
+ if (rv != 0) {
+ // Address does *not* exist
+ test("Returns error when host cannot be resolved: ");
+ // First see if this domain name *actually* resolves to NXDOMAIN
+ c = redisConnect(HIREDIS_BAD_DOMAIN, 6379);
+ test_cond(
+ c->err == REDIS_ERR_OTHER &&
+ (strcmp(c->errstr, "Name or service not known") == 0 ||
+ strcmp(c->errstr, "Can't resolve: " HIREDIS_BAD_DOMAIN) == 0 ||
+ strcmp(c->errstr, "Name does not resolve") == 0 ||
+ strcmp(c->errstr, "nodename nor servname provided, or not known") == 0 ||
+ strcmp(c->errstr, "No address associated with hostname") == 0 ||
+ strcmp(c->errstr, "Temporary failure in name resolution") == 0 ||
+ strcmp(c->errstr, "hostname nor servname provided, or not known") == 0 ||
+ strcmp(c->errstr, "no address associated with name") == 0 ||
+ strcmp(c->errstr, "No such host is known. ") == 0));
+ redisFree(c);
+ } else {
+ printf("Skipping NXDOMAIN test. Found evil ISP!\n");
+ freeaddrinfo(ai_tmp);
+ }
+
+#ifndef _WIN32
+ test("Returns error when the port is not open: ");
+ c = redisConnect((char*)"localhost", 1);
+ test_cond(c->err == REDIS_ERR_IO &&
+ strcmp(c->errstr,"Connection refused") == 0);
+ redisFree(c);
+
+ test("Returns error when the unix_sock socket path doesn't accept connections: ");
+ c = redisConnectUnix((char*)"/tmp/idontexist.sock");
+ test_cond(c->err == REDIS_ERR_IO); /* Don't care about the message... */
+ redisFree(c);
+#endif
+}
+
+/* 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);
+}
+
+/* Dummy function just to test setting a callback with redisOptions */
+void push_handler_async(redisAsyncContext *ac, void *reply) {
+ (void)ac;
+ (void)reply;
+}
+
+static void test_resp3_push_handler(redisContext *c) {
+ struct pushCounters pc = {0};
+ redisPushFn *old = NULL;
+ redisReply *reply;
+ void *privdata;
+
+ /* Switch to RESP3 and turn on client tracking */
+ send_hello(c, 3);
+ send_client_tracking(c, "ON");
+ privdata = c->privdata;
+ c->privdata = &pc;
+
+ reply = redisCommand(c, "GET key:0");
+ assert(reply != NULL);
+ freeReplyObject(reply);
+
+ test("RESP3 PUSH messages are handled out of band by default: ");
+ reply = redisCommand(c, "SET key:0 val:0");
+ test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS);
+ freeReplyObject(reply);
+
+ assert((reply = redisCommand(c, "GET key:0")) != NULL);
+ freeReplyObject(reply);
+
+ 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 && 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
+ * sure it is not handled out of band. */
+ test("With no handler, PUSH replies come in-band: ");
+ redisSetPushCallback(c, NULL);
+ assert((reply = redisCommand(c, "GET key:0")) != NULL);
+ freeReplyObject(reply);
+ assert((reply = redisCommand(c, "SET key:0 invalid")) != NULL);
+ test_cond(reply->type == REDIS_REPLY_PUSH);
+ freeReplyObject(reply);
+
+ test("With no PUSH handler, no replies are lost: ");
+ assert(redisGetReply(c, (void**)&reply) == REDIS_OK);
+ test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS);
+ freeReplyObject(reply);
+
+ /* Return to the originally set PUSH handler */
+ assert(old != NULL);
+ redisSetPushCallback(c, old);
+
+ /* Switch back to RESP2 and disable tracking */
+ c->privdata = privdata;
+ send_client_tracking(c, "OFF");
+ send_hello(c, 2);
+}
+
+redisOptions get_redis_tcp_options(struct config config) {
+ redisOptions options = {0};
+ REDIS_OPTIONS_SET_TCP(&options, config.tcp.host, config.tcp.port);
+ return options;
+}
+
+static void test_resp3_push_options(struct config config) {
+ redisAsyncContext *ac;
+ redisContext *c;
+ redisOptions options;
+
+ test("We set a default RESP3 handler for redisContext: ");
+ options = get_redis_tcp_options(config);
+ assert((c = redisConnectWithOptions(&options)) != NULL);
+ test_cond(c->push_cb != NULL);
+ redisFree(c);
+
+ test("We don't set a default RESP3 push handler for redisAsyncContext: ");
+ options = get_redis_tcp_options(config);
+ assert((ac = redisAsyncConnectWithOptions(&options)) != NULL);
+ test_cond(ac->c.push_cb == NULL);
+ redisAsyncFree(ac);
+
+ test("Our REDIS_OPT_NO_PUSH_AUTOFREE flag works: ");
+ options = get_redis_tcp_options(config);
+ options.options |= REDIS_OPT_NO_PUSH_AUTOFREE;
+ assert((c = redisConnectWithOptions(&options)) != NULL);
+ test_cond(c->push_cb == NULL);
+ redisFree(c);
+
+ test("We can use redisOptions to set a custom PUSH handler for redisContext: ");
+ options = get_redis_tcp_options(config);
+ options.push_cb = push_handler;
+ assert((c = redisConnectWithOptions(&options)) != NULL);
+ test_cond(c->push_cb == push_handler);
+ redisFree(c);
+
+ test("We can use redisOptions to set a custom PUSH handler for redisAsyncContext: ");
+ options = get_redis_tcp_options(config);
+ options.async_push_cb = push_handler_async;
+ assert((ac = redisAsyncConnectWithOptions(&options)) != NULL);
+ test_cond(ac->push_cb == push_handler_async);
+ redisAsyncFree(ac);
+}
+
+void free_privdata(void *privdata) {
+ struct privdata *data = privdata;
+ data->dtor_counter++;
+}
+
+static void test_privdata_hooks(struct config config) {
+ struct privdata data = {0};
+ redisOptions options;
+ redisContext *c;
+
+ test("We can use redisOptions to set privdata: ");
+ options = get_redis_tcp_options(config);
+ REDIS_OPTIONS_SET_PRIVDATA(&options, &data, free_privdata);
+ assert((c = redisConnectWithOptions(&options)) != NULL);
+ test_cond(c->privdata == &data);
+
+ test("Our privdata destructor fires when we free the context: ");
+ redisFree(c);
+ test_cond(data.dtor_counter == 1);
+}
+
+static void test_blocking_connection(struct config config) {
+ redisContext *c;
+ redisReply *reply;
+ int major;
+
+ c = do_connect(config);
+
+ test("Is able to deliver commands: ");
+ reply = redisCommand(c,"PING");
+ test_cond(reply->type == REDIS_REPLY_STATUS &&
+ strcasecmp(reply->str,"pong") == 0)
+ freeReplyObject(reply);
+
+ test("Is a able to send commands verbatim: ");
+ reply = redisCommand(c,"SET foo bar");
+ test_cond (reply->type == REDIS_REPLY_STATUS &&
+ strcasecmp(reply->str,"ok") == 0)
+ freeReplyObject(reply);
+
+ test("%%s String interpolation works: ");
+ reply = redisCommand(c,"SET %s %s","foo","hello world");
+ freeReplyObject(reply);
+ reply = redisCommand(c,"GET foo");
+ test_cond(reply->type == REDIS_REPLY_STRING &&
+ strcmp(reply->str,"hello world") == 0);
+ freeReplyObject(reply);
+
+ test("%%b String interpolation works: ");
+ reply = redisCommand(c,"SET %b %b","foo",(size_t)3,"hello\x00world",(size_t)11);
+ freeReplyObject(reply);
+ reply = redisCommand(c,"GET foo");
+ test_cond(reply->type == REDIS_REPLY_STRING &&
+ memcmp(reply->str,"hello\x00world",11) == 0)
+
+ test("Binary reply length is correct: ");
+ test_cond(reply->len == 11)
+ freeReplyObject(reply);
+
+ test("Can parse nil replies: ");
+ reply = redisCommand(c,"GET nokey");
+ test_cond(reply->type == REDIS_REPLY_NIL)
+ freeReplyObject(reply);
+
+ /* test 7 */
+ test("Can parse integer replies: ");
+ reply = redisCommand(c,"INCR mycounter");
+ test_cond(reply->type == REDIS_REPLY_INTEGER && reply->integer == 1)
+ freeReplyObject(reply);
+
+ test("Can parse multi bulk replies: ");
+ freeReplyObject(redisCommand(c,"LPUSH mylist foo"));
+ freeReplyObject(redisCommand(c,"LPUSH mylist bar"));
+ reply = redisCommand(c,"LRANGE mylist 0 -1");
+ test_cond(reply->type == REDIS_REPLY_ARRAY &&
+ reply->elements == 2 &&
+ !memcmp(reply->element[0]->str,"bar",3) &&
+ !memcmp(reply->element[1]->str,"foo",3))
+ freeReplyObject(reply);
+
+ /* m/e with multi bulk reply *before* other reply.
+ * specifically test ordering of reply items to parse. */
+ test("Can handle nested multi bulk replies: ");
+ freeReplyObject(redisCommand(c,"MULTI"));
+ freeReplyObject(redisCommand(c,"LRANGE mylist 0 -1"));
+ freeReplyObject(redisCommand(c,"PING"));
+ reply = (redisCommand(c,"EXEC"));
+ test_cond(reply->type == REDIS_REPLY_ARRAY &&
+ reply->elements == 2 &&
+ reply->element[0]->type == REDIS_REPLY_ARRAY &&
+ reply->element[0]->elements == 2 &&
+ !memcmp(reply->element[0]->element[0]->str,"bar",3) &&
+ !memcmp(reply->element[0]->element[1]->str,"foo",3) &&
+ reply->element[1]->type == REDIS_REPLY_STATUS &&
+ strcasecmp(reply->element[1]->str,"pong") == 0);
+ freeReplyObject(reply);
+
+ /* Make sure passing NULL to redisGetReply is safe */
+ test("Can pass NULL to redisGetReply: ");
+ assert(redisAppendCommand(c, "PING") == REDIS_OK);
+ test_cond(redisGetReply(c, NULL) == REDIS_OK);
+
+ get_redis_version(c, &major, NULL);
+ if (major >= 6) test_resp3_push_handler(c);
+ test_resp3_push_options(config);
+
+ test_privdata_hooks(config);
+
+ disconnect(c, 0);
+}
+
+/* Send DEBUG SLEEP 0 to detect if we have this command */
+static int detect_debug_sleep(redisContext *c) {
+ int detected;
+ redisReply *reply = redisCommand(c, "DEBUG SLEEP 0\r\n");
+
+ if (reply == NULL || c->err) {
+ const char *cause = c->err ? c->errstr : "(none)";
+ fprintf(stderr, "Error testing for DEBUG SLEEP (Redis error: %s), exiting\n", cause);
+ exit(-1);
+ }
+
+ detected = reply->type == REDIS_REPLY_STATUS;
+ freeReplyObject(reply);
+
+ return detected;
+}
+
+static void test_blocking_connection_timeouts(struct config config) {
+ redisContext *c;
+ redisReply *reply;
+ ssize_t s;
+ const char *sleep_cmd = "DEBUG SLEEP 3\r\n";
+ struct timeval tv;
+
+ c = do_connect(config);
+ test("Successfully completes a command when the timeout is not exceeded: ");
+ reply = redisCommand(c,"SET foo fast");
+ freeReplyObject(reply);
+ tv.tv_sec = 0;
+ tv.tv_usec = 10000;
+ redisSetTimeout(c, tv);
+ reply = redisCommand(c, "GET foo");
+ test_cond(reply != NULL && reply->type == REDIS_REPLY_STRING && memcmp(reply->str, "fast", 4) == 0);
+ freeReplyObject(reply);
+ disconnect(c, 0);
+
+ c = do_connect(config);
+ test("Does not return a reply when the command times out: ");
+ if (detect_debug_sleep(c)) {
+ redisAppendFormattedCommand(c, sleep_cmd, strlen(sleep_cmd));
+ s = c->funcs->write(c);
+ tv.tv_sec = 0;
+ tv.tv_usec = 10000;
+ redisSetTimeout(c, tv);
+ reply = redisCommand(c, "GET foo");
+#ifndef _WIN32
+ test_cond(s > 0 && reply == NULL && c->err == REDIS_ERR_IO &&
+ strcmp(c->errstr, "Resource temporarily unavailable") == 0);
+#else
+ test_cond(s > 0 && reply == NULL && c->err == REDIS_ERR_TIMEOUT &&
+ strcmp(c->errstr, "recv timeout") == 0);
+#endif
+ freeReplyObject(reply);
+ } else {
+ test_skipped();
+ }
+
+ test("Reconnect properly reconnects after a timeout: ");
+ do_reconnect(c, config);
+ reply = redisCommand(c, "PING");
+ test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS && strcmp(reply->str, "PONG") == 0);
+ freeReplyObject(reply);
+
+ test("Reconnect properly uses owned parameters: ");
+ config.tcp.host = "foo";
+ config.unix_sock.path = "foo";
+ do_reconnect(c, config);
+ reply = redisCommand(c, "PING");
+ test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS && strcmp(reply->str, "PONG") == 0);
+ freeReplyObject(reply);
+
+ disconnect(c, 0);
+}
+
+static void test_blocking_io_errors(struct config config) {
+ redisContext *c;
+ redisReply *reply;
+ void *_reply;
+ int major, minor;
+
+ /* Connect to target given by config. */
+ c = do_connect(config);
+ get_redis_version(c, &major, &minor);
+
+ test("Returns I/O error when the connection is lost: ");
+ reply = redisCommand(c,"QUIT");
+ if (major > 2 || (major == 2 && minor > 0)) {
+ /* > 2.0 returns OK on QUIT and read() should be issued once more
+ * to know the descriptor is at EOF. */
+ test_cond(strcasecmp(reply->str,"OK") == 0 &&
+ redisGetReply(c,&_reply) == REDIS_ERR);
+ freeReplyObject(reply);
+ } else {
+ test_cond(reply == NULL);
+ }
+
+#ifndef _WIN32
+ /* On 2.0, QUIT will cause the connection to be closed immediately and
+ * the read(2) for the reply on QUIT will set the error to EOF.
+ * On >2.0, QUIT will return with OK and another read(2) needed to be
+ * issued to find out the socket was closed by the server. In both
+ * conditions, the error will be set to EOF. */
+ assert(c->err == REDIS_ERR_EOF &&
+ strcmp(c->errstr,"Server closed the connection") == 0);
+#endif
+ redisFree(c);
+
+ c = do_connect(config);
+ test("Returns I/O error on socket timeout: ");
+ struct timeval tv = { 0, 1000 };
+ assert(redisSetTimeout(c,tv) == REDIS_OK);
+ int respcode = redisGetReply(c,&_reply);
+#ifndef _WIN32
+ test_cond(respcode == REDIS_ERR && c->err == REDIS_ERR_IO && errno == EAGAIN);
+#else
+ test_cond(respcode == REDIS_ERR && c->err == REDIS_ERR_TIMEOUT);
+#endif
+ redisFree(c);
+}
+
+static void test_invalid_timeout_errors(struct config config) {
+ redisContext *c;
+
+ test("Set error when an invalid timeout usec value is given to redisConnectWithTimeout: ");
+
+ config.tcp.timeout.tv_sec = 0;
+ config.tcp.timeout.tv_usec = 10000001;
+
+ c = redisConnectWithTimeout(config.tcp.host, config.tcp.port, config.tcp.timeout);
+
+ test_cond(c->err == REDIS_ERR_IO && strcmp(c->errstr, "Invalid timeout specified") == 0);
+ redisFree(c);
+
+ test("Set error when an invalid timeout sec value is given to redisConnectWithTimeout: ");
+
+ config.tcp.timeout.tv_sec = (((LONG_MAX) - 999) / 1000) + 1;
+ config.tcp.timeout.tv_usec = 0;
+
+ c = redisConnectWithTimeout(config.tcp.host, config.tcp.port, config.tcp.timeout);
+
+ test_cond(c->err == REDIS_ERR_IO && strcmp(c->errstr, "Invalid timeout specified") == 0);
+ redisFree(c);
+}
+
+/* Wrap malloc to abort on failure so OOM checks don't make the test logic
+ * harder to follow. */
+void *hi_malloc_safe(size_t size) {
+ void *ptr = hi_malloc(size);
+ if (ptr == NULL) {
+ fprintf(stderr, "Error: Out of memory\n");
+ exit(-1);
+ }
+
+ return ptr;
+}
+
+static void test_throughput(struct config config) {
+ redisContext *c = do_connect(config);
+ redisReply **replies;
+ int i, num;
+ long long t1, t2;
+
+ test("Throughput:\n");
+ for (i = 0; i < 500; i++)
+ freeReplyObject(redisCommand(c,"LPUSH mylist foo"));
+
+ num = 1000;
+ replies = hi_malloc_safe(sizeof(redisReply*)*num);
+ t1 = usec();
+ for (i = 0; i < num; i++) {
+ replies[i] = redisCommand(c,"PING");
+ assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_STATUS);
+ }
+ t2 = usec();
+ for (i = 0; i < num; i++) freeReplyObject(replies[i]);
+ hi_free(replies);
+ printf("\t(%dx PING: %.3fs)\n", num, (t2-t1)/1000000.0);
+
+ replies = hi_malloc_safe(sizeof(redisReply*)*num);
+ t1 = usec();
+ for (i = 0; i < num; i++) {
+ replies[i] = redisCommand(c,"LRANGE mylist 0 499");
+ assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_ARRAY);
+ assert(replies[i] != NULL && replies[i]->elements == 500);
+ }
+ t2 = usec();
+ for (i = 0; i < num; i++) freeReplyObject(replies[i]);
+ hi_free(replies);
+ printf("\t(%dx LRANGE with 500 elements: %.3fs)\n", num, (t2-t1)/1000000.0);
+
+ replies = hi_malloc_safe(sizeof(redisReply*)*num);
+ t1 = usec();
+ for (i = 0; i < num; i++) {
+ replies[i] = redisCommand(c, "INCRBY incrkey %d", 1000000);
+ assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_INTEGER);
+ }
+ t2 = usec();
+ for (i = 0; i < num; i++) freeReplyObject(replies[i]);
+ hi_free(replies);
+ printf("\t(%dx INCRBY: %.3fs)\n", num, (t2-t1)/1000000.0);
+
+ num = 10000;
+ replies = hi_malloc_safe(sizeof(redisReply*)*num);
+ for (i = 0; i < num; i++)
+ redisAppendCommand(c,"PING");
+ t1 = usec();
+ for (i = 0; i < num; i++) {
+ assert(redisGetReply(c, (void*)&replies[i]) == REDIS_OK);
+ assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_STATUS);
+ }
+ t2 = usec();
+ for (i = 0; i < num; i++) freeReplyObject(replies[i]);
+ hi_free(replies);
+ printf("\t(%dx PING (pipelined): %.3fs)\n", num, (t2-t1)/1000000.0);
+
+ replies = hi_malloc_safe(sizeof(redisReply*)*num);
+ for (i = 0; i < num; i++)
+ redisAppendCommand(c,"LRANGE mylist 0 499");
+ t1 = usec();
+ for (i = 0; i < num; i++) {
+ assert(redisGetReply(c, (void*)&replies[i]) == REDIS_OK);
+ assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_ARRAY);
+ assert(replies[i] != NULL && replies[i]->elements == 500);
+ }
+ t2 = usec();
+ for (i = 0; i < num; i++) freeReplyObject(replies[i]);
+ hi_free(replies);
+ printf("\t(%dx LRANGE with 500 elements (pipelined): %.3fs)\n", num, (t2-t1)/1000000.0);
+
+ replies = hi_malloc_safe(sizeof(redisReply*)*num);
+ for (i = 0; i < num; i++)
+ redisAppendCommand(c,"INCRBY incrkey %d", 1000000);
+ t1 = usec();
+ for (i = 0; i < num; i++) {
+ assert(redisGetReply(c, (void*)&replies[i]) == REDIS_OK);
+ assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_INTEGER);
+ }
+ t2 = usec();
+ for (i = 0; i < num; i++) freeReplyObject(replies[i]);
+ hi_free(replies);
+ printf("\t(%dx INCRBY (pipelined): %.3fs)\n", num, (t2-t1)/1000000.0);
+
+ disconnect(c, 0);
+}
+
+// static long __test_callback_flags = 0;
+// static void __test_callback(redisContext *c, void *privdata) {
+// ((void)c);
+// /* Shift to detect execution order */
+// __test_callback_flags <<= 8;
+// __test_callback_flags |= (long)privdata;
+// }
+//
+// static void __test_reply_callback(redisContext *c, redisReply *reply, void *privdata) {
+// ((void)c);
+// /* Shift to detect execution order */
+// __test_callback_flags <<= 8;
+// __test_callback_flags |= (long)privdata;
+// if (reply) freeReplyObject(reply);
+// }
+//
+// static redisContext *__connect_nonblock() {
+// /* Reset callback flags */
+// __test_callback_flags = 0;
+// return redisConnectNonBlock("127.0.0.1", port, NULL);
+// }
+//
+// static void test_nonblocking_connection() {
+// redisContext *c;
+// int wdone = 0;
+//
+// test("Calls command callback when command is issued: ");
+// c = __connect_nonblock();
+// redisSetCommandCallback(c,__test_callback,(void*)1);
+// redisCommand(c,"PING");
+// test_cond(__test_callback_flags == 1);
+// redisFree(c);
+//
+// test("Calls disconnect callback on redisDisconnect: ");
+// c = __connect_nonblock();
+// redisSetDisconnectCallback(c,__test_callback,(void*)2);
+// redisDisconnect(c);
+// test_cond(__test_callback_flags == 2);
+// redisFree(c);
+//
+// test("Calls disconnect callback and free callback on redisFree: ");
+// c = __connect_nonblock();
+// redisSetDisconnectCallback(c,__test_callback,(void*)2);
+// redisSetFreeCallback(c,__test_callback,(void*)4);
+// redisFree(c);
+// test_cond(__test_callback_flags == ((2 << 8) | 4));
+//
+// test("redisBufferWrite against empty write buffer: ");
+// c = __connect_nonblock();
+// test_cond(redisBufferWrite(c,&wdone) == REDIS_OK && wdone == 1);
+// redisFree(c);
+//
+// test("redisBufferWrite against not yet connected fd: ");
+// c = __connect_nonblock();
+// redisCommand(c,"PING");
+// test_cond(redisBufferWrite(c,NULL) == REDIS_ERR &&
+// strncmp(c->error,"write:",6) == 0);
+// redisFree(c);
+//
+// test("redisBufferWrite against closed fd: ");
+// c = __connect_nonblock();
+// redisCommand(c,"PING");
+// redisDisconnect(c);
+// test_cond(redisBufferWrite(c,NULL) == REDIS_ERR &&
+// strncmp(c->error,"write:",6) == 0);
+// redisFree(c);
+//
+// test("Process callbacks in the right sequence: ");
+// c = __connect_nonblock();
+// redisCommandWithCallback(c,__test_reply_callback,(void*)1,"PING");
+// redisCommandWithCallback(c,__test_reply_callback,(void*)2,"PING");
+// redisCommandWithCallback(c,__test_reply_callback,(void*)3,"PING");
+//
+// /* Write output buffer */
+// wdone = 0;
+// while(!wdone) {
+// usleep(500);
+// redisBufferWrite(c,&wdone);
+// }
+//
+// /* Read until at least one callback is executed (the 3 replies will
+// * arrive in a single packet, causing all callbacks to be executed in
+// * a single pass). */
+// while(__test_callback_flags == 0) {
+// assert(redisBufferRead(c) == REDIS_OK);
+// redisProcessCallbacks(c);
+// }
+// test_cond(__test_callback_flags == 0x010203);
+// redisFree(c);
+//
+// test("redisDisconnect executes pending callbacks with NULL reply: ");
+// c = __connect_nonblock();
+// redisSetDisconnectCallback(c,__test_callback,(void*)1);
+// redisCommandWithCallback(c,__test_reply_callback,(void*)2,"PING");
+// redisDisconnect(c);
+// test_cond(__test_callback_flags == 0x0201);
+// 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 = {
+ .host = "127.0.0.1",
+ .port = 6379
+ },
+ .unix_sock = {
+ .path = "/tmp/redis.sock"
+ }
+ };
+ int throughput = 1;
+ int test_inherit_fd = 1;
+ int skips_as_fails = 0;
+ int test_unix_socket;
+
+ /* Parse command line options. */
+ argv++; argc--;
+ while (argc) {
+ if (argc >= 2 && !strcmp(argv[0],"-h")) {
+ argv++; argc--;
+ cfg.tcp.host = argv[0];
+ } else if (argc >= 2 && !strcmp(argv[0],"-p")) {
+ argv++; argc--;
+ cfg.tcp.port = atoi(argv[0]);
+ } else if (argc >= 2 && !strcmp(argv[0],"-s")) {
+ argv++; argc--;
+ cfg.unix_sock.path = argv[0];
+ } else if (argc >= 1 && !strcmp(argv[0],"--skip-throughput")) {
+ throughput = 0;
+ } else if (argc >= 1 && !strcmp(argv[0],"--skip-inherit-fd")) {
+ test_inherit_fd = 0;
+ } else if (argc >= 1 && !strcmp(argv[0],"--skips-as-fails")) {
+ skips_as_fails = 1;
+#ifdef HIREDIS_TEST_SSL
+ } else if (argc >= 2 && !strcmp(argv[0],"--ssl-port")) {
+ argv++; argc--;
+ cfg.ssl.port = atoi(argv[0]);
+ } else if (argc >= 2 && !strcmp(argv[0],"--ssl-host")) {
+ argv++; argc--;
+ cfg.ssl.host = argv[0];
+ } else if (argc >= 2 && !strcmp(argv[0],"--ssl-ca-cert")) {
+ argv++; argc--;
+ cfg.ssl.ca_cert = argv[0];
+ } else if (argc >= 2 && !strcmp(argv[0],"--ssl-cert")) {
+ argv++; argc--;
+ cfg.ssl.cert = argv[0];
+ } else if (argc >= 2 && !strcmp(argv[0],"--ssl-key")) {
+ argv++; argc--;
+ cfg.ssl.key = argv[0];
+#endif
+ } else {
+ fprintf(stderr, "Invalid argument: %s\n", argv[0]);
+ exit(1);
+ }
+ argv++; argc--;
+ }
+
+#ifndef _WIN32
+ /* Ignore broken pipe signal (for I/O error tests). */
+ signal(SIGPIPE, SIG_IGN);
+
+ test_unix_socket = access(cfg.unix_sock.path, F_OK) == 0;
+
+#else
+ /* Unix sockets don't exist in Windows */
+ test_unix_socket = 0;
+#endif
+
+ test_allocator_injection();
+
+ test_format_commands();
+ test_reply_reader();
+ test_blocking_connection_errors();
+ test_free_null();
+
+ printf("\nTesting against TCP connection (%s:%d):\n", cfg.tcp.host, cfg.tcp.port);
+ cfg.type = CONN_TCP;
+ test_blocking_connection(cfg);
+ test_blocking_connection_timeouts(cfg);
+ test_blocking_io_errors(cfg);
+ test_invalid_timeout_errors(cfg);
+ test_append_formatted_commands(cfg);
+ if (throughput) test_throughput(cfg);
+
+ printf("\nTesting against Unix socket connection (%s): ", cfg.unix_sock.path);
+ if (test_unix_socket) {
+ printf("\n");
+ cfg.type = CONN_UNIX;
+ test_blocking_connection(cfg);
+ test_blocking_connection_timeouts(cfg);
+ test_blocking_io_errors(cfg);
+ if (throughput) test_throughput(cfg);
+ } else {
+ test_skipped();
+ }
+
+#ifdef HIREDIS_TEST_SSL
+ if (cfg.ssl.port && cfg.ssl.host) {
+
+ redisInitOpenSSL();
+ _ssl_ctx = redisCreateSSLContext(cfg.ssl.ca_cert, NULL, cfg.ssl.cert, cfg.ssl.key, NULL, NULL);
+ assert(_ssl_ctx != NULL);
+
+ printf("\nTesting against SSL connection (%s:%d):\n", cfg.ssl.host, cfg.ssl.port);
+ cfg.type = CONN_SSL;
+
+ test_blocking_connection(cfg);
+ test_blocking_connection_timeouts(cfg);
+ test_blocking_io_errors(cfg);
+ test_invalid_timeout_errors(cfg);
+ test_append_formatted_commands(cfg);
+ if (throughput) test_throughput(cfg);
+
+ redisFreeSSLContext(_ssl_ctx);
+ _ssl_ctx = NULL;
+ }
+#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) {
+ printf("\n");
+ cfg.type = CONN_FD;
+ test_blocking_connection(cfg);
+ } else {
+ test_skipped();
+ }
+ }
+
+ if (fails || (skips_as_fails && skips)) {
+ printf("*** %d TESTS FAILED ***\n", fails);
+ if (skips) {
+ printf("*** %d TESTS SKIPPED ***\n", skips);
+ }
+ return 1;
+ }
+
+ printf("ALL TESTS PASSED (%d skipped)\n", skips);
+ return 0;
+}
diff --git a/deps/hiredis/test.sh b/deps/hiredis/test.sh
new file mode 100755
index 0000000..c72bcb0
--- /dev/null
+++ b/deps/hiredis/test.sh
@@ -0,0 +1,78 @@
+#!/bin/sh -ue
+
+REDIS_SERVER=${REDIS_SERVER:-redis-server}
+REDIS_PORT=${REDIS_PORT:-56379}
+REDIS_SSL_PORT=${REDIS_SSL_PORT:-56443}
+TEST_SSL=${TEST_SSL:-0}
+SKIPS_AS_FAILS=${SKIPS_AS_FAILS-:0}
+SSL_TEST_ARGS=
+SKIPS_ARG=
+
+tmpdir=$(mktemp -d)
+PID_FILE=${tmpdir}/hiredis-test-redis.pid
+SOCK_FILE=${tmpdir}/hiredis-test-redis.sock
+
+if [ "$TEST_SSL" = "1" ]; then
+ SSL_CA_CERT=${tmpdir}/ca.crt
+ SSL_CA_KEY=${tmpdir}/ca.key
+ SSL_CERT=${tmpdir}/redis.crt
+ SSL_KEY=${tmpdir}/redis.key
+
+ openssl genrsa -out ${tmpdir}/ca.key 4096
+ openssl req \
+ -x509 -new -nodes -sha256 \
+ -key ${SSL_CA_KEY} \
+ -days 3650 \
+ -subj '/CN=Hiredis Test CA' \
+ -out ${SSL_CA_CERT}
+ openssl genrsa -out ${SSL_KEY} 2048
+ openssl req \
+ -new -sha256 \
+ -key ${SSL_KEY} \
+ -subj '/CN=Hiredis Test Cert' | \
+ openssl x509 \
+ -req -sha256 \
+ -CA ${SSL_CA_CERT} \
+ -CAkey ${SSL_CA_KEY} \
+ -CAserial ${tmpdir}/ca.txt \
+ -CAcreateserial \
+ -days 365 \
+ -out ${SSL_CERT}
+
+ SSL_TEST_ARGS="--ssl-host 127.0.0.1 --ssl-port ${REDIS_SSL_PORT} --ssl-ca-cert ${SSL_CA_CERT} --ssl-cert ${SSL_CERT} --ssl-key ${SSL_KEY}"
+fi
+
+cleanup() {
+ set +e
+ kill $(cat ${PID_FILE})
+ rm -rf ${tmpdir}
+}
+trap cleanup INT TERM EXIT
+
+cat > ${tmpdir}/redis.conf <<EOF
+daemonize yes
+pidfile ${PID_FILE}
+port ${REDIS_PORT}
+bind 127.0.0.1
+unixsocket ${SOCK_FILE}
+EOF
+
+if [ "$TEST_SSL" = "1" ]; then
+ cat >> ${tmpdir}/redis.conf <<EOF
+tls-port ${REDIS_SSL_PORT}
+tls-ca-cert-file ${SSL_CA_CERT}
+tls-cert-file ${SSL_CERT}
+tls-key-file ${SSL_KEY}
+EOF
+fi
+
+cat ${tmpdir}/redis.conf
+${REDIS_SERVER} ${tmpdir}/redis.conf
+
+# Wait until we detect the unix socket
+while [ ! -S "${SOCK_FILE}" ]; do sleep 1; done
+
+# Treat skips as failures if directed
+[ "$SKIPS_AS_FAILS" = 1 ] && SKIPS_ARG="--skips-as-fails"
+
+${TEST_PREFIX:-} ./hiredis-test -h 127.0.0.1 -p ${REDIS_PORT} -s ${SOCK_FILE} ${SSL_TEST_ARGS} ${SKIPS_ARG}
diff --git a/deps/hiredis/win32.h b/deps/hiredis/win32.h
new file mode 100644
index 0000000..04289c6
--- /dev/null
+++ b/deps/hiredis/win32.h
@@ -0,0 +1,56 @@
+#ifndef _WIN32_HELPER_INCLUDE
+#define _WIN32_HELPER_INCLUDE
+#ifdef _MSC_VER
+
+#include <winsock2.h> /* for struct timeval */
+
+#ifndef inline
+#define inline __inline
+#endif
+
+#ifndef strcasecmp
+#define strcasecmp stricmp
+#endif
+
+#ifndef strncasecmp
+#define strncasecmp strnicmp
+#endif
+
+#ifndef va_copy
+#define va_copy(d,s) ((d) = (s))
+#endif
+
+#ifndef snprintf
+#define snprintf c99_snprintf
+
+__inline int c99_vsnprintf(char* str, size_t size, const char* format, va_list ap)
+{
+ int count = -1;
+
+ if (size != 0)
+ count = _vsnprintf_s(str, size, _TRUNCATE, format, ap);
+ if (count == -1)
+ count = _vscprintf(format, ap);
+
+ return count;
+}
+
+__inline int c99_snprintf(char* str, size_t size, const char* format, ...)
+{
+ int count;
+ va_list ap;
+
+ va_start(ap, format);
+ count = c99_vsnprintf(str, size, format, ap);
+ va_end(ap);
+
+ return count;
+}
+#endif
+#endif /* _MSC_VER */
+
+#ifdef _WIN32
+#define strerror_r(errno,buf,len) strerror_s(buf,len,errno)
+#endif /* _WIN32 */
+
+#endif /* _WIN32_HELPER_INCLUDE */