[
  {
    "path": ".github/release-drafter-config.yml",
    "content": "name-template: '$NEXT_MAJOR_VERSION'\ntag-template: 'v$NEXT_MAJOR_VERSION'\nautolabeler:\n  - label: 'maintenance'\n    files:\n      - '*.md'\n      - '.github/*'\n  - label: 'bug'\n    branch:\n      - '/bug-.+'\n  - label: 'maintenance'\n    branch:\n      - '/maintenance-.+'\n  - label: 'feature'\n    branch:\n      - '/feature-.+'\ncategories:\n  - title: 'Breaking Changes'\n    labels:\n      - 'breakingchange'\n\n  - title: '🧪 Experimental Features'\n    labels:\n      - 'experimental'\n  - title: '🚀 New Features'\n    labels:\n      - 'feature'\n      - 'enhancement'\n  - title: '🐛 Bug Fixes'\n    labels:\n      - 'fix'\n      - 'bugfix'\n      - 'bug'\n      - 'BUG'\n  - title: '🧰 Maintenance'\n    label: 'maintenance'\nchange-template: '- $TITLE @$AUTHOR (#$NUMBER)'\nexclude-labels:\n  - 'skip-changelog'\ntemplate: |\n  ## Changes\n\n  $CHANGES\n\n  ## Contributors\n  We'd like to thank all the contributors who worked on this release!\n\n  $CONTRIBUTORS\n\n"
  },
  {
    "path": ".github/spellcheck-settings.yml",
    "content": "matrix:\n- name: Markdown\n  expect_match: false\n  apsell:\n    lang: en\n    d: en_US\n    ignore-case: true\n  dictionary:\n    wordlists:\n    - .github/wordlist.txt\n    output: wordlist.dic\n  pipeline:\n  - pyspelling.filters.markdown:\n      markdown_extensions:\n      - markdown.extensions.extra:\n  - pyspelling.filters.html:\n      comments: false\n      attributes:\n      - alt\n      ignores:\n      - ':matches(code, pre)'\n      - code\n      - pre\n      - blockquote\n      - img\n  sources:\n  - 'README.md'\n  - 'FAQ.md'\n  - 'docs/**'\n"
  },
  {
    "path": ".github/wordlist.txt",
    "content": "ABI\nACLs\nalloc\nAllocator\nallocators\nantirez\napi\nAPIs\nASYNC\nasyncRedisContext\nasyncronous\nAUTOFREE\nautoload\nautoloader\nautoloading\nAutoloading\nbackend\nbackends\nbehaviour\nboolean\nCAS\nChangelog\ncustomizable\nCustomizable\nCVE\ndataset\nde\ndeallocation\nElastiCache\nextensibility\nFPM\ngetaddrinfo\ngmail\ngrunder\nGrunder\nhiredis\nHiredis\nHIREDIS\nhostname\nIANA\nIPv\nIPV\nkeepalive\nkeyspace\nkeyspaces\nKiB\nlibc\nlibev\nlibevent\nlocalhost\nLua\nmichael\nminimalistic\nnamespace\nNOAUTOFREE\nNOAUTOFREEREPLIES\nNONBLOCK\nNoordhuis\nOpenSSL\nPackagist\npcnoordhuis\nPhpRedis\nPieter\npipelined\npipelining\npluggable\nPredis\nPRERELEASE\nprintf\nPSR\nPSUBSCRIBE\nrb\nReadme\nREADME\nrebalanced\nrebalancing\nredis\nRedis\nredisAsyncContext\nredisContext\nredisOptions\nredisReader\nreusability\nREUSEADDR\nruntime\nSanfilippo\nSHA\nsharding\nSONAME\nSSL\nstruct\nstunnel\nsubelements\nTCP\nTLS\nunparsed\nUNSPEC\nURI\nvariadic\n"
  },
  {
    "path": ".github/workflows/build.yml",
    "content": "name: Build and test\non: [push, pull_request]\n\njobs:\n  ubuntu:\n    name: Ubuntu\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v3\n\n      - name: Install dependencies\n        run: |\n          curl -fsSL https://packages.redis.io/gpg | sudo gpg --dearmor -o /usr/share/keyrings/redis-archive-keyring.gpg\n          echo \"deb [signed-by=/usr/share/keyrings/redis-archive-keyring.gpg] https://packages.redis.io/deb $(lsb_release -cs) main\" | sudo tee /etc/apt/sources.list.d/redis.list\n          sudo apt-get update\n          sudo apt-get install -y redis-server valgrind libevent-dev\n\n      - name: Build using cmake\n        env:\n          EXTRA_CMAKE_OPTS: -DENABLE_EXAMPLES:BOOL=ON -DENABLE_SSL:BOOL=ON -DENABLE_SSL_TESTS:BOOL=ON -DENABLE_ASYNC_TESTS:BOOL=ON\n          CFLAGS: -Werror\n          CXXFLAGS: -Werror\n        run: mkdir build && cd build && cmake .. && make\n\n      - name: Build using makefile\n        run: USE_SSL=1 TEST_ASYNC=1 make\n\n      - name: Run tests\n        env:\n          SKIPS_AS_FAILS: 1\n          TEST_SSL: 1\n        run: $GITHUB_WORKSPACE/test.sh\n\n      #      - name: Run tests under valgrind\n      #        env:\n      #          SKIPS_AS_FAILS: 1\n      #          TEST_PREFIX: valgrind --error-exitcode=99 --track-origins=yes --leak-check=full\n      #        run: $GITHUB_WORKSPACE/test.sh\n\n  centos8:\n    name: RockyLinux 8\n    runs-on: ubuntu-latest\n    container: rockylinux:8\n    steps:\n      - uses: actions/checkout@v3\n\n      - name: Install dependencies\n        run: |\n          dnf -y upgrade --refresh\n          dnf -y install https://rpms.remirepo.net/enterprise/remi-release-8.rpm\n          dnf -y module install redis:remi-6.0\n          dnf -y group install \"Development Tools\"\n          dnf -y install openssl-devel cmake valgrind libevent-devel\n\n      - name: Build using cmake\n        env:\n          EXTRA_CMAKE_OPTS: -DENABLE_EXAMPLES:BOOL=ON -DENABLE_SSL:BOOL=ON -DENABLE_SSL_TESTS:BOOL=ON -DENABLE_ASYNC_TESTS:BOOL=ON\n          CFLAGS: -Werror\n          CXXFLAGS: -Werror\n        run: mkdir build && cd build && cmake .. && make\n\n      - name: Build using Makefile\n        run: USE_SSL=1 TEST_ASYNC=1 make\n\n      - name: Run tests\n        env:\n          SKIPS_AS_FAILS: 1\n          TEST_SSL: 1\n        run: $GITHUB_WORKSPACE/test.sh\n\n      - name: Run tests under valgrind\n        env:\n          SKIPS_AS_FAILS: 1\n          TEST_SSL: 1\n          TEST_PREFIX: valgrind --error-exitcode=99 --track-origins=yes --leak-check=full\n        run: $GITHUB_WORKSPACE/test.sh\n\n  freebsd:\n    runs-on: ubuntu-latest\n    name:  FreeBSD\n    steps:\n      - uses: actions/checkout@v3\n\n      - name: Build in FreeBSD\n        uses: vmactions/freebsd-vm@v1.0.5\n        with:\n          prepare: pkg install -y gmake cmake\n          run: |\n            mkdir build && cd build && cmake .. && make && cd ..\n            gmake\n\n  macos:\n    name: macOS\n    runs-on: macos-latest\n    steps:\n      - uses: actions/checkout@v3\n\n      - name: Install dependencies\n        run: |\n          brew install openssl redis\n          brew link redis --force\n\n      - name: Build hiredis\n        run: USE_SSL=1 make\n\n      - name: Run tests\n        env:\n          TEST_SSL: 1\n        run: $GITHUB_WORKSPACE/test.sh\n\n  windows:\n    name: Windows\n    runs-on: windows-latest\n    steps:\n      - uses: actions/checkout@v3\n\n      - name: Install dependencies\n        run: |\n          choco install -y ninja memurai-developer\n\n      - uses: ilammy/msvc-dev-cmd@v1\n      - name: Build hiredis\n        run: |\n          mkdir build && cd build\n          cmake .. -G Ninja -DCMAKE_BUILD_TYPE=Release -DENABLE_EXAMPLES=ON\n          ninja -v\n\n      - name: Run tests\n        run: |\n          ./build/hiredis-test.exe\n\n      - name: Install Cygwin Action\n        uses: cygwin/cygwin-install-action@v2\n        with:\n          packages: make git gcc-core\n\n      - name: Build in cygwin\n        env:\n          HIREDIS_PATH: ${{ github.workspace }}\n        run: |\n          make clean && make\n\n  test-cmake-version:\n    name: Test build with CMake ${{ matrix.cmake-version }}\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        cmake-version: [\n          '3.7.0', # Minimum version\n          '3.22.0', # Ubuntu 22.04 LTS\n          '3.28.0', # Ubuntu 24.04 LTS\n          '4.0.0' # Latest version\n        ]\n    steps:\n      - uses: actions/checkout@v4\n      - name: Setup CMake\n        uses: jwlawson/actions-setup-cmake@v2\n        with:\n          cmake-version: ${{ matrix.cmake-version }}\n      - name: Install hiredis dependencies\n        run: |\n          sudo apt-get install -y libssl-dev libevent-dev\n      - name: Build\n        run: |\n          mkdir build && cd build\n          cmake -DENABLE_SSL=ON -DENABLE_SSL_TESTS=ON -DENABLE_ASYNC_TESTS=ON -DENABLE_EXAMPLES=ON ..\n          make\n"
  },
  {
    "path": ".github/workflows/release-drafter.yml",
    "content": "name: Release Drafter\n\non:\n  push:\n    # branches to consider in the event; optional, defaults to all\n    branches:\n      - master\n\njobs:\n  update_release_draft:\n    runs-on: ubuntu-latest\n    steps:\n      # Drafts your next Release notes as Pull Requests are merged into \"master\"\n      - uses: release-drafter/release-drafter@v5\n        with:\n          # (Optional) specify config name to use, relative to .github/. Default: release-drafter.yml\n           config-name: release-drafter-config.yml\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/spellcheck.yml",
    "content": "name: spellcheck\non:\n  pull_request:\njobs:\n  check-spelling:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v3\n      - name: Check Spelling\n        uses: rojopolis/spellcheck-github-actions@0.33.1\n        with:\n          config_path: .github/spellcheck-settings.yml\n          task_name: Markdown\n"
  },
  {
    "path": ".github/workflows/test.yml",
    "content": "name: C/C++ CI\n\non:\n  push:\n    branches: [ master ]\n  pull_request:\n    branches: [ master ]\n\njobs:\n  full-build:\n    name: Build all, plus default examples, run tests against redis\n    runs-on: ubuntu-latest\n    env:\n      # the docker image used by the test.sh\n      REDIS_DOCKER: redis:alpine\n\n    steps:\n    - name: Install prerequisites\n      run: sudo apt-get update && sudo apt-get install -y libev-dev libevent-dev libglib2.0-dev libssl-dev valgrind\n    - uses: actions/checkout@v3\n    - name: Run make\n      run: make all examples\n    - name: Run unittests\n      run: make check\n    - name: Run tests with valgrind\n      env:\n        TEST_PREFIX: valgrind --error-exitcode=100\n        SKIPS_ARG: --skip-throughput\n      run: make check\n\n  build-32-bit:\n    name: Build and test minimal 32 bit linux\n    runs-on: ubuntu-latest\n    steps:\n    - name: Install prerequisites\n      run: sudo apt-get update && sudo apt-get install gcc-multilib\n    - uses: actions/checkout@v3\n    - name: Run make\n      run: make all\n      env:\n        PLATFORM_FLAGS: -m32\n    - name: Run unittests\n      env:\n        REDIS_DOCKER: redis:alpine\n      run: make check\n\n  build-arm:\n    name: Cross-compile and test arm linux with Qemu\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        include:\n          - name: arm\n            toolset: arm-linux-gnueabi\n            emulator: qemu-arm\n          - name: aarch64\n            toolset: aarch64-linux-gnu\n            emulator: qemu-aarch64\n\n    steps:\n    - name: Install qemu\n      if: matrix.emulator\n      run: sudo apt-get update && sudo apt-get install -y qemu-user\n    - name: Install platform toolset\n      if: matrix.toolset\n      run: sudo apt-get install -y gcc-${{matrix.toolset}}\n    - uses: actions/checkout@v3\n    - name: Run make\n      run: make all\n      env:\n        CC: ${{matrix.toolset}}-gcc\n        AR: ${{matrix.toolset}}-ar\n    - name: Run unittests\n      env:\n        REDIS_DOCKER: redis:alpine\n        TEST_PREFIX: ${{matrix.emulator}} -L /usr/${{matrix.toolset}}/\n      run: make check\n\n  build-windows:\n    name: Build and test on windows 64 bit Intel\n    runs-on: windows-latest\n    steps:\n      - uses: microsoft/setup-msbuild@v1.0.2\n      - uses: actions/checkout@v3\n      - name: Run CMake (shared lib)\n        run: cmake -Wno-dev CMakeLists.txt\n      - name: Build hiredis (shared lib)\n        run: MSBuild hiredis.vcxproj /p:Configuration=Debug\n      - name: Run CMake (static lib)\n        run: cmake -Wno-dev CMakeLists.txt -DBUILD_SHARED_LIBS=OFF\n      - name: Build hiredis (static lib)\n        run: MSBuild hiredis.vcxproj /p:Configuration=Debug\n      - name: Build hiredis-test\n        run: MSBuild hiredis-test.vcxproj /p:Configuration=Debug\n      # use memurai, redis compatible server, since it is easy to install.  Can't\n      # install official redis containers on the windows runner\n      - name: Install Memurai redis server\n        run: choco install -y memurai-developer.install\n      - name: Run tests\n        run: Debug\\hiredis-test.exe\n"
  },
  {
    "path": ".gitignore",
    "content": "/hiredis-test\n/examples/hiredis-example*\n/*.o\n/*.so\n/*.dylib\n/*.a\n/*.pc\n*.dSYM\ntags\ncompile_commands.json\n"
  },
  {
    "path": ".travis.yml",
    "content": "language: c\ncompiler:\n  - gcc\n  - clang\n\nos:\n  - linux\n  - osx\n\ndist: bionic\n\nbranches:\n  only:\n    - staging\n    - trying\n    - master\n    - /^release\\/.*$/\n\ninstall:\n    - if [ \"$TRAVIS_COMPILER\" != \"mingw\" ]; then\n        wget https://github.com/redis/redis/archive/6.0.6.tar.gz;\n        tar -xzvf 6.0.6.tar.gz;\n        pushd redis-6.0.6 && BUILD_TLS=yes make && export PATH=$PWD/src:$PATH && popd;\n      fi;\n\nbefore_script:\n    - if [ \"$TRAVIS_OS_NAME\" == \"osx\" ]; then\n        curl -O https://distfiles.macports.org/MacPorts/MacPorts-2.6.2-10.13-HighSierra.pkg;\n        sudo installer -pkg MacPorts-2.6.2-10.13-HighSierra.pkg -target /;\n        export PATH=$PATH:/opt/local/bin && sudo port -v selfupdate;\n        sudo port -N install openssl redis;\n      fi;\n\naddons:\n  apt:\n    packages:\n    - libc6-dbg\n    - libc6-dev\n    - libc6:i386\n    - libc6-dev-i386\n    - libc6-dbg:i386\n    - gcc-multilib\n    - g++-multilib\n    - libssl-dev\n    - libssl-dev:i386\n    - valgrind\n\nenv:\n  - BITS=\"32\"\n  - BITS=\"64\"\n\nscript:\n  - EXTRA_CMAKE_OPTS=\"-DENABLE_EXAMPLES:BOOL=ON -DENABLE_SSL:BOOL=ON -DENABLE_SSL_TESTS:BOOL=ON\";\n    if [ \"$TRAVIS_OS_NAME\" == \"osx\" ]; then\n      if [ \"$BITS\" == \"32\" ]; then\n        CFLAGS=\"-m32 -Werror\";\n        CXXFLAGS=\"-m32 -Werror\";\n        LDFLAGS=\"-m32\";\n        EXTRA_CMAKE_OPTS=;\n      else\n        CFLAGS=\"-Werror\";\n        CXXFLAGS=\"-Werror\";\n      fi;\n    else\n      TEST_PREFIX=\"valgrind --track-origins=yes --leak-check=full\";\n      if [ \"$BITS\" == \"32\" ]; then\n        CFLAGS=\"-m32 -Werror\";\n        CXXFLAGS=\"-m32 -Werror\";\n        LDFLAGS=\"-m32\";\n        EXTRA_CMAKE_OPTS=;\n      else\n        CFLAGS=\"-Werror\";\n        CXXFLAGS=\"-Werror\";\n      fi;\n    fi;\n    export CFLAGS CXXFLAGS LDFLAGS TEST_PREFIX EXTRA_CMAKE_OPTS\n  - make && make clean;\n    if [ \"$TRAVIS_OS_NAME\" == \"osx\" ]; then\n      if [ \"$BITS\" == \"64\" ]; then\n        OPENSSL_PREFIX=\"$(ls -d /usr/local/Cellar/openssl@1.1/*)\" USE_SSL=1 make;\n      fi;\n    else\n      USE_SSL=1 make;\n    fi;\n  - mkdir build/ && cd build/\n  - cmake .. ${EXTRA_CMAKE_OPTS}\n  - make VERBOSE=1\n  - if [ \"$BITS\" == \"64\" ]; then\n      TEST_SSL=1 SKIPS_AS_FAILS=1 ctest -V;\n    else\n      SKIPS_AS_FAILS=1 ctest -V;\n    fi;\n\njobs:\n  include:\n    # Windows MinGW cross compile on Linux\n    - os: linux\n      dist: xenial\n      compiler: mingw\n      addons:\n        apt:\n          packages:\n            - ninja-build\n            - gcc-mingw-w64-x86-64\n            - g++-mingw-w64-x86-64\n      script:\n        - mkdir build && cd build\n        - 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\n        - ninja -v\n\n    # Windows MSVC 2017\n    - os: windows\n      compiler: msvc\n      env:\n        - MATRIX_EVAL=\"CC=cl.exe && CXX=cl.exe\"\n      before_install:\n        - eval \"${MATRIX_EVAL}\"\n      install:\n        - choco install ninja\n        - choco install -y memurai-developer\n      script:\n        - mkdir build && cd build\n        - cmd.exe //C 'C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\BuildTools\\VC\\Auxiliary\\Build\\vcvarsall.bat' amd64 '&&'\n          cmake .. -G Ninja -DCMAKE_BUILD_TYPE=Release -DENABLE_EXAMPLES=ON '&&' ninja -v\n        - ./hiredis-test.exe\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Hiredis Changelog\n\nSince [v1.1.0-rc1](https://github.com/redis/hiredis/releases/tag/v1.1.0-rc1) we track changelog using [GitHub releases](https://github.com/redis/hiredis/releases).\nBelow you can find the changelog for all versions prior to that.\n\n-----------------\n\n## [1.0.2](https://github.com/redis/hiredis/tree/v1.0.2) - (2021-10-07)\n\nAnnouncing Hiredis v1.0.2, which fixes CVE-2021-32765 but returns the SONAME to the correct value of `1.0.0`.\n\n- [Revert SONAME bump](https://github.com/redis/hiredis/commit/d4e6f109a064690cde64765c654e679fea1d3548)\n  ([Michael Grunder](https://github.com/michael-grunder))\n\n## [1.0.1](https://github.com/redis/hiredis/tree/v1.0.1) - (2021-10-04)\n\n<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>\n\nAnnouncing Hiredis v1.0.1, a security release fixing CVE-2021-32765\n\n- Fix for [CVE-2021-32765](https://github.com/redis/hiredis/security/advisories/GHSA-hfm9-39pp-55p2)\n  [commit](https://github.com/redis/hiredis/commit/76a7b10005c70babee357a7d0f2becf28ec7ed1e)\n  ([Yossi Gottlieb](https://github.com/yossigo))\n\n_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:\n\n## [1.0.0](https://github.com/redis/hiredis/tree/v1.0.0) - (2020-08-03)\n\nAnnouncing Hiredis v1.0.0, which adds support for RESP3, SSL connections, allocator injection, and better Windows support! :tada:\n\n_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:\n\n[Michael Grunder](https://github.com/michael-grunder), [Yossi Gottlieb](https://github.com/yossigo),\n[Mark Nunberg](https://github.com/mnunberg), [Marcus Geelnard](https://github.com/mbitsnbites),\n[Justin Brewer](https://github.com/justinbrewer), [Valentino Geron](https://github.com/valentinogeron),\n[Minun Dragonation](https://github.com/dragonation), [Omri Steiner](https://github.com/OmriSteiner),\n[Sangmoon Yi](https://github.com/jman-krafton), [Jinjiazh](https://github.com/jinjiazhang),\n[Odin Hultgren Van Der Horst](https://github.com/Miniwoffer), [Muhammad Zahalqa](https://github.com/tryfinally),\n[Nick Rivera](https://github.com/heronr), [Qi Yang](https://github.com/movebean),\n[kevin1018](https://github.com/kevin1018)\n\n[Full Changelog](https://github.com/redis/hiredis/compare/v0.14.1...v1.0.0)\n\n**BREAKING CHANGES**:\n\n* `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))\n\n* Bulk and multi-bulk lengths less than -1 or greater than `LLONG_MAX` are now protocol errors. This is consistent\n  with the RESP specification. On 32-bit platforms, the upper bound is lowered to `SIZE_MAX`.\n\n* `redisReplyObjectFunctions.createArray` now takes `size_t` for its length parameter.\n\n**New features:**\n- Support for RESP3\n  [\\#697](https://github.com/redis/hiredis/pull/697),\n  [\\#805](https://github.com/redis/hiredis/pull/805),\n  [\\#819](https://github.com/redis/hiredis/pull/819),\n  [\\#841](https://github.com/redis/hiredis/pull/841)\n  ([Yossi Gottlieb](https://github.com/yossigo), [Michael Grunder](https://github.com/michael-grunder))\n- Support for SSL connections\n  [\\#645](https://github.com/redis/hiredis/pull/645),\n  [\\#699](https://github.com/redis/hiredis/pull/699),\n  [\\#702](https://github.com/redis/hiredis/pull/702),\n  [\\#708](https://github.com/redis/hiredis/pull/708),\n  [\\#711](https://github.com/redis/hiredis/pull/711),\n  [\\#821](https://github.com/redis/hiredis/pull/821),\n  [more](https://github.com/redis/hiredis/pulls?q=is%3Apr+is%3Amerged+SSL)\n  ([Mark Nunberg](https://github.com/mnunberg), [Yossi Gottlieb](https://github.com/yossigo))\n- Run-time allocator injection\n  [\\#800](https://github.com/redis/hiredis/pull/800)\n  ([Michael Grunder](https://github.com/michael-grunder))\n- Improved Windows support (including MinGW and Windows CI)\n  [\\#652](https://github.com/redis/hiredis/pull/652),\n  [\\#663](https://github.com/redis/hiredis/pull/663)\n  ([Marcus Geelnard](https://www.bitsnbites.eu/author/m/))\n- Adds support for distinct connect and command timeouts\n  [\\#839](https://github.com/redis/hiredis/pull/839),\n  [\\#829](https://github.com/redis/hiredis/pull/829)\n  ([Valentino Geron](https://github.com/valentinogeron))\n- Add generic pointer and destructor to `redisContext` that users can use for context.\n  [\\#855](https://github.com/redis/hiredis/pull/855)\n  ([Michael Grunder](https://github.com/michael-grunder))\n\n**Closed issues (that involved code changes):**\n\n- Makefile does not install TLS libraries  [\\#809](https://github.com/redis/hiredis/issues/809)\n- 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))\n- Fix integer overflow in `sdsrange` [\\#827](https://github.com/redis/hiredis/issues/827)\n- INFO & CLUSTER commands failed when using RESP3 [\\#802](https://github.com/redis/hiredis/issues/802)\n- 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)\n- RESP3 PUSH messages incorrectly use pending callback [\\#825](https://github.com/redis/hiredis/issues/825)\n- Asynchronous PSUBSCRIBE command fails when using RESP3 [\\#815](https://github.com/redis/hiredis/issues/815)\n- New SSL API [\\#804](https://github.com/redis/hiredis/issues/804), [\\#813](https://github.com/redis/hiredis/issues/813)\n- Hard-coded limit of nested reply depth [\\#794](https://github.com/redis/hiredis/issues/794)\n- 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),\n- Added timers to libev adapter.  [\\#778](https://github.com/redis/hiredis/issues/778), [\\#795](https://github.com/redis/hiredis/pull/795)\n- Initialization discards const qualifier [\\#777](https://github.com/redis/hiredis/issues/777)\n- \\[BUG\\]\\[MinGW64\\] Error setting socket timeout  [\\#775](https://github.com/redis/hiredis/issues/775)\n- undefined reference to hi_malloc [\\#769](https://github.com/redis/hiredis/issues/769)\n- hiredis pkg-config file incorrectly ignores multiarch libdir spec'n [\\#767](https://github.com/redis/hiredis/issues/767)\n- Don't use -G to build shared object on Solaris [\\#757](https://github.com/redis/hiredis/issues/757)\n- error when make USE\\_SSL=1 [\\#748](https://github.com/redis/hiredis/issues/748)\n- Allow to change SSL Mode [\\#646](https://github.com/redis/hiredis/issues/646)\n- hiredis/adapters/libevent.h memleak [\\#618](https://github.com/redis/hiredis/issues/618)\n- redisLibuvPoll crash when server closes the connetion [\\#545](https://github.com/redis/hiredis/issues/545)\n- about redisAsyncDisconnect question [\\#518](https://github.com/redis/hiredis/issues/518)\n- hiredis adapters libuv error for help [\\#508](https://github.com/redis/hiredis/issues/508)\n- API/ABI changes analysis [\\#506](https://github.com/redis/hiredis/issues/506)\n- Memory leak patch in Redis [\\#502](https://github.com/redis/hiredis/issues/502)\n- Remove the depth limitation [\\#421](https://github.com/redis/hiredis/issues/421)\n\n**Merged pull requests:**\n\n- Move SSL management to a distinct private pointer [\\#855](https://github.com/redis/hiredis/pull/855) ([michael-grunder](https://github.com/michael-grunder))\n- Move include to sockcompat.h to maintain style [\\#850](https://github.com/redis/hiredis/pull/850) ([michael-grunder](https://github.com/michael-grunder))\n- Remove erroneous tag and add license to push example [\\#849](https://github.com/redis/hiredis/pull/849) ([michael-grunder](https://github.com/michael-grunder))\n- fix windows compiling with mingw [\\#848](https://github.com/redis/hiredis/pull/848) ([rmalizia44](https://github.com/rmalizia44))\n- Some Windows quality of life improvements. [\\#846](https://github.com/redis/hiredis/pull/846) ([michael-grunder](https://github.com/michael-grunder))\n- Use \\_WIN32 define instead of WIN32 [\\#845](https://github.com/redis/hiredis/pull/845) ([michael-grunder](https://github.com/michael-grunder))\n- Non Linux CI fixes [\\#844](https://github.com/redis/hiredis/pull/844) ([michael-grunder](https://github.com/michael-grunder))\n- Resp3 oob push support [\\#841](https://github.com/redis/hiredis/pull/841) ([michael-grunder](https://github.com/michael-grunder))\n- fix \\#785: defer TCP\\_NODELAY in async tcp connections [\\#836](https://github.com/redis/hiredis/pull/836) ([OmriSteiner](https://github.com/OmriSteiner))\n- sdsrange overflow fix [\\#830](https://github.com/redis/hiredis/pull/830) ([michael-grunder](https://github.com/michael-grunder))\n- Use explicit pointer casting for c++ compatibility [\\#826](https://github.com/redis/hiredis/pull/826) ([aureus1](https://github.com/aureus1))\n- Document allocator injection and completeness fix in test.c [\\#824](https://github.com/redis/hiredis/pull/824) ([michael-grunder](https://github.com/michael-grunder))\n- Use unique names for allocator struct members [\\#823](https://github.com/redis/hiredis/pull/823) ([michael-grunder](https://github.com/michael-grunder))\n- New SSL API to replace redisSecureConnection\\(\\). [\\#821](https://github.com/redis/hiredis/pull/821) ([yossigo](https://github.com/yossigo))\n- Add logic to handle RESP3 push messages [\\#819](https://github.com/redis/hiredis/pull/819) ([michael-grunder](https://github.com/michael-grunder))\n- Use standrad isxdigit instead of custom helper function. [\\#814](https://github.com/redis/hiredis/pull/814) ([tryfinally](https://github.com/tryfinally))\n- Fix missing SSL build/install options. [\\#812](https://github.com/redis/hiredis/pull/812) ([yossigo](https://github.com/yossigo))\n- Add link to ABI tracker [\\#808](https://github.com/redis/hiredis/pull/808) ([michael-grunder](https://github.com/michael-grunder))\n- Resp3 verbatim string support [\\#805](https://github.com/redis/hiredis/pull/805) ([michael-grunder](https://github.com/michael-grunder))\n- Allow users to replace allocator and handle OOM everywhere. [\\#800](https://github.com/redis/hiredis/pull/800) ([michael-grunder](https://github.com/michael-grunder))\n- Remove nested depth limitation. [\\#797](https://github.com/redis/hiredis/pull/797) ([michael-grunder](https://github.com/michael-grunder))\n- Attempt to fix compilation on Solaris [\\#796](https://github.com/redis/hiredis/pull/796) ([michael-grunder](https://github.com/michael-grunder))\n- Support timeouts in libev adapater [\\#795](https://github.com/redis/hiredis/pull/795) ([michael-grunder](https://github.com/michael-grunder))\n- Fix pkgconfig when installing to a custom lib dir [\\#793](https://github.com/redis/hiredis/pull/793) ([michael-grunder](https://github.com/michael-grunder))\n- 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))\n- Use correct libuv call on Windows [\\#784](https://github.com/redis/hiredis/pull/784) ([michael-grunder](https://github.com/michael-grunder))\n- 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))\n- CMake: Set hiredis\\_ssl shared object version. [\\#780](https://github.com/redis/hiredis/pull/780) ([yossigo](https://github.com/yossigo))\n- Win32 tests and timeout fix [\\#776](https://github.com/redis/hiredis/pull/776) ([michael-grunder](https://github.com/michael-grunder))\n- Provides an optional cleanup callback for async data. [\\#768](https://github.com/redis/hiredis/pull/768) ([heronr](https://github.com/heronr))\n- Housekeeping fixes [\\#764](https://github.com/redis/hiredis/pull/764) ([michael-grunder](https://github.com/michael-grunder))\n- install alloc.h [\\#756](https://github.com/redis/hiredis/pull/756) ([ch1aki](https://github.com/ch1aki))\n- fix spelling mistakes [\\#746](https://github.com/redis/hiredis/pull/746) ([ShooterIT](https://github.com/ShooterIT))\n- Free the reply in redisGetReply when passed NULL [\\#741](https://github.com/redis/hiredis/pull/741) ([michael-grunder](https://github.com/michael-grunder))\n- Fix dead code in sslLogCallback relating to should\\_log variable. [\\#737](https://github.com/redis/hiredis/pull/737) ([natoscott](https://github.com/natoscott))\n- Fix typo in dict.c. [\\#731](https://github.com/redis/hiredis/pull/731) ([Kevin-Xi](https://github.com/Kevin-Xi))\n- Adding an option to DISABLE\\_TESTS [\\#727](https://github.com/redis/hiredis/pull/727) ([pbotros](https://github.com/pbotros))\n- Update README with SSL support. [\\#720](https://github.com/redis/hiredis/pull/720) ([yossigo](https://github.com/yossigo))\n- Fixes leaks in unit tests [\\#715](https://github.com/redis/hiredis/pull/715) ([michael-grunder](https://github.com/michael-grunder))\n- SSL Tests [\\#711](https://github.com/redis/hiredis/pull/711) ([yossigo](https://github.com/yossigo))\n- SSL Reorganization [\\#708](https://github.com/redis/hiredis/pull/708) ([yossigo](https://github.com/yossigo))\n- Fix MSVC build. [\\#706](https://github.com/redis/hiredis/pull/706) ([yossigo](https://github.com/yossigo))\n- SSL: Properly report SSL\\_connect\\(\\) errors. [\\#702](https://github.com/redis/hiredis/pull/702) ([yossigo](https://github.com/yossigo))\n- Silent SSL trace to stdout by default. [\\#699](https://github.com/redis/hiredis/pull/699) ([yossigo](https://github.com/yossigo))\n- Port RESP3 support from Redis. [\\#697](https://github.com/redis/hiredis/pull/697) ([yossigo](https://github.com/yossigo))\n- Removed whitespace before newline [\\#691](https://github.com/redis/hiredis/pull/691) ([Miniwoffer](https://github.com/Miniwoffer))\n- Add install adapters header files [\\#688](https://github.com/redis/hiredis/pull/688) ([kevin1018](https://github.com/kevin1018))\n- Remove unnecessary null check before free [\\#684](https://github.com/redis/hiredis/pull/684) ([qlyoung](https://github.com/qlyoung))\n- redisReaderGetReply leak memory [\\#671](https://github.com/redis/hiredis/pull/671) ([movebean](https://github.com/movebean))\n- fix timeout code in windows [\\#670](https://github.com/redis/hiredis/pull/670) ([jman-krafton](https://github.com/jman-krafton))\n- test: fix errstr matching for musl libc [\\#665](https://github.com/redis/hiredis/pull/665) ([ghost](https://github.com/ghost))\n- Windows: MinGW fixes and Windows Travis builders [\\#663](https://github.com/redis/hiredis/pull/663) ([mbitsnbites](https://github.com/mbitsnbites))\n- 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))\n- Fix Compile Error On Windows \\(Visual Studio\\) [\\#658](https://github.com/redis/hiredis/pull/658) ([jinjiazhang](https://github.com/jinjiazhang))\n- Fix NXDOMAIN test case [\\#653](https://github.com/redis/hiredis/pull/653) ([michael-grunder](https://github.com/michael-grunder))\n- Add MinGW support [\\#652](https://github.com/redis/hiredis/pull/652) ([mbitsnbites](https://github.com/mbitsnbites))\n- SSL Support [\\#645](https://github.com/redis/hiredis/pull/645) ([mnunberg](https://github.com/mnunberg))\n- Fix Invalid argument after redisAsyncConnectUnix [\\#644](https://github.com/redis/hiredis/pull/644) ([codehz](https://github.com/codehz))\n- Makefile: use predefined AR [\\#632](https://github.com/redis/hiredis/pull/632) ([Mic92](https://github.com/Mic92))\n- FreeBSD  build fix [\\#628](https://github.com/redis/hiredis/pull/628) ([devnexen](https://github.com/devnexen))\n- Fix errors not propagating properly with libuv.h. [\\#624](https://github.com/redis/hiredis/pull/624) ([yossigo](https://github.com/yossigo))\n- Update README.md [\\#621](https://github.com/redis/hiredis/pull/621) ([Crunsher](https://github.com/Crunsher))\n- Fix redisBufferRead documentation [\\#620](https://github.com/redis/hiredis/pull/620) ([hacst](https://github.com/hacst))\n- Add CPPFLAGS to REAL\\_CFLAGS [\\#614](https://github.com/redis/hiredis/pull/614) ([thomaslee](https://github.com/thomaslee))\n- Update createArray to take size\\_t [\\#597](https://github.com/redis/hiredis/pull/597) ([justinbrewer](https://github.com/justinbrewer))\n- fix common realloc mistake and add null check more [\\#580](https://github.com/redis/hiredis/pull/580) ([charsyam](https://github.com/charsyam))\n- Proper error reporting for connect failures [\\#578](https://github.com/redis/hiredis/pull/578) ([mnunberg](https://github.com/mnunberg))\n\n\\* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)*\n\n## [1.0.0-rc1](https://github.com/redis/hiredis/tree/v1.0.0-rc1) - (2020-07-29)\n\n_Note:  There were no changes to code between v1.0.0-rc1 and v1.0.0 so see v1.0.0 for changelog_\n\n### 0.14.1 (2020-03-13)\n\n* Adds safe allocation wrappers (CVE-2020-7105, #747, #752) (Michael Grunder)\n\n### 0.14.0 (2018-09-25)\n**BREAKING CHANGES**:\n\n* Change `redisReply.len` to `size_t`, as it denotes the the size of a string\n\n  User code should compare this to `size_t` values as well.\n  If it was used to compare to other values, casting might be necessary or can be removed, if casting was applied before.\n\n* Make string2ll static to fix conflict with Redis (Tom Lee [c3188b])\n* Use -dynamiclib instead of -shared for OSX (Ryan Schmidt [a65537])\n* Use string2ll from Redis w/added tests (Michael Grunder [7bef04, 60f622])\n* Makefile - OSX compilation fixes (Ryan Schmidt [881fcb, 0e9af8])\n* Remove redundant NULL checks (Justin Brewer [54acc8, 58e6b8])\n* Fix bulk and multi-bulk length truncation (Justin Brewer [109197])\n* Fix SIGSEGV in OpenBSD by checking for NULL before calling freeaddrinfo (Justin Brewer [546d94])\n* Several POSIX compatibility fixes (Justin Brewer [bbeab8, 49bbaa, d1c1b6])\n* Makefile - Compatibility fixes (Dimitri Vorobiev [3238cf, 12a9d1])\n* Makefile - Fix make install on FreeBSD (Zach Shipko [a2ef2b])\n* Makefile - don't assume $(INSTALL) is cp (Igor Gnatenko [725a96])\n* Separate side-effect causing function from assert and small cleanup (amallia [b46413, 3c3234])\n* Don't send negative values to `__redisAsyncCommand` (Frederik Deweerdt [706129])\n* Fix leak if setsockopt fails (Frederik Deweerdt [e21c9c])\n* Fix libevent leak (zfz [515228])\n* Clean up GCC warning (Ichito Nagata [2ec774])\n* Keep track of errno in `__redisSetErrorFromErrno()` as snprintf may use it (Jin Qing [25cd88])\n* Solaris compilation fix (Donald Whyte [41b07d])\n* Reorder linker arguments when building examples (Tustfarm-heart [06eedd])\n* Keep track of subscriptions in case of rapid subscribe/unsubscribe (Hyungjin Kim [073dc8, be76c5, d46999])\n* libuv use after free fix (Paul Scott [cbb956])\n* Properly close socket fd on reconnect attempt (WSL [64d1ec])\n* Skip valgrind in OSX tests (Jan-Erik Rediger [9deb78])\n* Various updates for Travis testing OSX (Ted Nyman [fa3774, 16a459, bc0ea5])\n* Update libevent (Chris Xin [386802])\n* Change sds.h for building in C++ projects (Ali Volkan ATLI [f5b32e])\n* Use proper format specifier in redisFormatSdsCommandArgv (Paulino Huerta, Jan-Erik Rediger [360a06, 8655a6])\n* Better handling of NULL reply in example code (Jan-Erik Rediger [1b8ed3])\n* Prevent overflow when formatting an error (Jan-Erik Rediger [0335cb])\n* Compatibility fix for strerror_r (Tom Lee [bb1747])\n* Properly detect integer parse/overflow errors (Justin Brewer [93421f])\n* Adds CI for Windows and cygwin fixes (owent, [6c53d6, 6c3e40])\n* Catch a buffer overflow when formatting the error message\n* Import latest upstream sds. This breaks applications that are linked against the old hiredis v0.13\n* Fix warnings, when compiled with -Wshadow\n* Make hiredis compile in Cygwin on Windows, now CI-tested\n* Bulk and multi-bulk lengths less than -1 or greater than `LLONG_MAX` are now\n  protocol errors. This is consistent with the RESP specification. On 32-bit\n  platforms, the upper bound is lowered to `SIZE_MAX`.\n\n* Remove backwards compatibility macro's\n\nThis removes the following old function aliases, use the new name now:\n\n| Old                         | New                    |\n| --------------------------- | ---------------------- |\n| redisReplyReaderCreate      | redisReaderCreate      |\n| redisReplyReaderCreate      | redisReaderCreate      |\n| redisReplyReaderFree        | redisReaderFree        |\n| redisReplyReaderFeed        | redisReaderFeed        |\n| redisReplyReaderGetReply    | redisReaderGetReply    |\n| redisReplyReaderSetPrivdata | redisReaderSetPrivdata |\n| redisReplyReaderGetObject   | redisReaderGetObject   |\n| redisReplyReaderGetError    | redisReaderGetError    |\n\n* The `DEBUG` variable in the Makefile was renamed to `DEBUG_FLAGS`\n\nPreviously it broke some builds for people that had `DEBUG` set to some arbitrary value,\ndue to debugging other software.\nBy renaming we avoid unintentional name clashes.\n\nSimply rename `DEBUG` to `DEBUG_FLAGS` in your environment to make it working again.\n\n### 0.13.3 (2015-09-16)\n\n* Revert \"Clear `REDIS_CONNECTED` flag when connection is closed\".\n* Make tests pass on FreeBSD (Thanks, Giacomo Olgeni)\n\n\nIf the `REDIS_CONNECTED` flag is cleared,\nthe async onDisconnect callback function will never be called.\nThis causes problems as the disconnect is never reported back to the user.\n\n### 0.13.2 (2015-08-25)\n\n* Prevent crash on pending replies in async code (Thanks, @switch-st)\n* Clear `REDIS_CONNECTED` flag when connection is closed (Thanks, Jerry Jacobs)\n* Add MacOS X addapter (Thanks, @dizzus)\n* Add Qt adapter (Thanks, Pietro Cerutti)\n* Add Ivykis adapter (Thanks, Gergely Nagy)\n\nAll adapters are provided as is and are only tested where possible.\n\n### 0.13.1 (2015-05-03)\n\nThis is a bug fix release.\nThe new `reconnect` method introduced new struct members, which clashed with pre-defined names in pre-C99 code.\nAnother commit forced C99 compilation just to make it work, but of course this is not desirable for outside projects.\nOther non-C99 code can now use hiredis as usual again.\nSorry for the inconvenience.\n\n* Fix memory leak in async reply handling (Salvatore Sanfilippo)\n* Rename struct member to avoid name clash with pre-c99 code (Alex Balashov, ncopa)\n\n### 0.13.0 (2015-04-16)\n\nThis release adds a minimal Windows compatibility layer.\nThe parser, standalone since v0.12.0, can now be compiled on Windows\n(and thus used in other client libraries as well)\n\n* Windows compatibility layer for parser code (tzickel)\n* Properly escape data printed to PKGCONF file (Dan Skorupski)\n* Fix tests when assert() undefined (Keith Bennett, Matt Stancliff)\n* Implement a reconnect method for the client context, this changes the structure of `redisContext` (Aaron Bedra)\n\n### 0.12.1 (2015-01-26)\n\n* Fix `make install`: DESTDIR support, install all required files, install PKGCONF in proper location\n* Fix `make test` as 32 bit build on 64 bit platform\n\n### 0.12.0 (2015-01-22)\n\n* Add optional KeepAlive support\n\n* Try again on EINTR errors\n\n* Add libuv adapter\n\n* Add IPv6 support\n\n* Remove possibility of multiple close on same fd\n\n* Add ability to bind source address on connect\n\n* Add redisConnectFd() and redisFreeKeepFd()\n\n* Fix getaddrinfo() memory leak\n\n* Free string if it is unused (fixes memory leak)\n\n* Improve redisAppendCommandArgv performance 2.5x\n\n* Add support for SO_REUSEADDR\n\n* Fix redisvFormatCommand format parsing\n\n* Add GLib 2.0 adapter\n\n* Refactor reading code into read.c\n\n* Fix errno error buffers to not clobber errors\n\n* Generate pkgconf during build\n\n* Silence _BSD_SOURCE warnings\n\n* Improve digit counting for multibulk creation\n\n\n### 0.11.0\n\n* Increase the maximum multi-bulk reply depth to 7.\n\n* Increase the read buffer size from 2k to 16k.\n\n* Use poll(2) instead of select(2) to support large fds (>= 1024).\n\n### 0.10.1\n\n* Makefile overhaul. Important to check out if you override one or more\n  variables using environment variables or via arguments to the \"make\" tool.\n\n* Issue #45: Fix potential memory leak for a multi bulk reply with 0 elements\n  being created by the default reply object functions.\n\n* Issue #43: Don't crash in an asynchronous context when Redis returns an error\n  reply after the connection has been made (this happens when the maximum\n  number of connections is reached).\n\n### 0.10.0\n\n* See commit log.\n"
  },
  {
    "path": "CMakeLists.txt",
    "content": "CMAKE_MINIMUM_REQUIRED(VERSION 3.7.0)\n\nMACRO(getVersionBit name)\n  SET(VERSION_REGEX \"^#define ${name} (.+)$\")\n  FILE(STRINGS \"${CMAKE_CURRENT_SOURCE_DIR}/hiredis.h\"\n    VERSION_BIT REGEX ${VERSION_REGEX})\n  STRING(REGEX REPLACE ${VERSION_REGEX} \"\\\\1\" ${name} \"${VERSION_BIT}\")\nENDMACRO(getVersionBit)\n\ngetVersionBit(HIREDIS_MAJOR)\ngetVersionBit(HIREDIS_MINOR)\ngetVersionBit(HIREDIS_PATCH)\ngetVersionBit(HIREDIS_SONAME)\nSET(VERSION \"${HIREDIS_MAJOR}.${HIREDIS_MINOR}.${HIREDIS_PATCH}\")\nMESSAGE(\"Detected version: ${VERSION}\")\n\nPROJECT(hiredis LANGUAGES \"C\" VERSION \"${VERSION}\")\n\n#Built as a subproject or if it is the main project\nset(MAIN_PROJECT OFF)\nif (CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR)\n    set(MAIN_PROJECT ON)\nendif()\n\nINCLUDE(GNUInstallDirs)\n\nOPTION(BUILD_SHARED_LIBS \"Build shared libraries\" ON)\nOPTION(ENABLE_SSL \"Build hiredis_ssl for SSL support\" OFF)\nOPTION(DISABLE_TESTS \"If tests should be compiled or not\" OFF)\nOPTION(ENABLE_SSL_TESTS \"Should we test SSL connections\" OFF)\nOPTION(ENABLE_EXAMPLES \"Enable building hiredis examples\" OFF)\nOPTION(ENABLE_ASYNC_TESTS \"Should we run all asynchronous API tests\" OFF)\n# Historically, the NuGet file was always install; default\n# to ON for those who rely on that historical behaviour.\nOPTION(ENABLE_NUGET \"Install NuGET packaging details\" ON)\n\n# Hiredis requires C99\nSET(CMAKE_C_STANDARD 99)\nSET(CMAKE_DEBUG_POSTFIX d)\n\nSET(hiredis_sources\n    alloc.c\n    async.c\n    hiredis.c\n    net.c\n    read.c\n    sds.c\n    sockcompat.c)\n\nSET(hiredis_sources ${hiredis_sources})\n\nIF(WIN32)\n    ADD_DEFINITIONS(-D_CRT_SECURE_NO_WARNINGS -DWIN32_LEAN_AND_MEAN)\nENDIF()\n\nADD_LIBRARY(hiredis ${hiredis_sources})\nADD_LIBRARY(hiredis::hiredis ALIAS hiredis)\nset(hiredis_export_name hiredis CACHE STRING \"Name of the exported target\")\nset_target_properties(hiredis PROPERTIES EXPORT_NAME ${hiredis_export_name})\n\nSET_TARGET_PROPERTIES(hiredis\n    PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS TRUE\n    VERSION \"${VERSION}\"\n    SOVERSION \"${HIREDIS_MAJOR}\")\nIF(MSVC)\n    SET_TARGET_PROPERTIES(hiredis\n        PROPERTIES COMPILE_FLAGS /Z7)\nENDIF()\nIF(WIN32)\n    TARGET_LINK_LIBRARIES(hiredis PUBLIC ws2_32 crypt32)\nELSEIF(CMAKE_SYSTEM_NAME MATCHES \"FreeBSD\")\n    TARGET_LINK_LIBRARIES(hiredis PUBLIC m)\nELSEIF(CMAKE_SYSTEM_NAME MATCHES \"SunOS\")\n    TARGET_LINK_LIBRARIES(hiredis PUBLIC socket)\nENDIF()\n\nTARGET_INCLUDE_DIRECTORIES(hiredis PUBLIC $<INSTALL_INTERFACE:include> $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>)\n\nCONFIGURE_FILE(hiredis.pc.in hiredis.pc @ONLY)\n\nif (MAIN_PROJECT)\n  set(CPACK_PACKAGE_VENDOR \"Redis\")\n  set(CPACK_PACKAGE_DESCRIPTION \"\\\n  Hiredis is a minimalistic C client library for the Redis database.\n  \n  It is minimalistic because it just adds minimal support for the protocol, \\\n  but at the same time it uses a high level printf-alike API in order to make \\\n  it much higher level than otherwise suggested by its minimal code base and the \\\n  lack of explicit bindings for every Redis command.\n  \n  Apart from supporting sending commands and receiving replies, it comes with a \\\n  reply parser that is decoupled from the I/O layer. It is a stream parser designed \\\n  for easy reusability, which can for instance be used in higher level language bindings \\\n  for efficient reply parsing.\n  \n  Hiredis only supports the binary-safe Redis protocol, so you can use it with any Redis \\\n  version >= 1.2.0.\n  \n  The library comes with multiple APIs. There is the synchronous API, the asynchronous API \\\n  and the reply parsing API.\")\n  set(CPACK_PACKAGE_HOMEPAGE_URL \"https://github.com/redis/hiredis\")\n  set(CPACK_PACKAGE_CONTACT \"michael dot grunder at gmail dot com\")\n  set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON)\n  set(CPACK_RPM_PACKAGE_AUTOREQPROV ON)\n\n  if (NOT CPack_CMake_INCLUDED)  \n    include(CPack)\n  endif()\nendif()\n\nINSTALL(TARGETS hiredis\n    EXPORT hiredis-targets\n    RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}\n    LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}\n    ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR})\n\nif (MSVC AND BUILD_SHARED_LIBS)\n    INSTALL(FILES $<TARGET_PDB_FILE:hiredis>\n        DESTINATION ${CMAKE_INSTALL_BINDIR}\n        CONFIGURATIONS Debug RelWithDebInfo)\nendif()\n\nif (ENABLE_NUGET)\n    # For NuGet packages\n    INSTALL(FILES hiredis.targets\n        DESTINATION build/native)\nendif()\n\nINSTALL(FILES hiredis.h read.h sds.h async.h alloc.h sockcompat.h\n    DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/hiredis)\n\nINSTALL(DIRECTORY adapters\n    DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/hiredis)\n\nINSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/hiredis.pc\n    DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig)\n\nexport(EXPORT hiredis-targets\n    FILE \"${CMAKE_CURRENT_BINARY_DIR}/hiredis-targets.cmake\"\n    NAMESPACE hiredis::)\n\nif(WIN32)\n    SET(CMAKE_CONF_INSTALL_DIR share/hiredis)\nelse()\n    SET(CMAKE_CONF_INSTALL_DIR ${CMAKE_INSTALL_LIBDIR}/cmake/hiredis)\nendif()\nSET(INCLUDE_INSTALL_DIR include)\ninclude(CMakePackageConfigHelpers)\nwrite_basic_package_version_file(\"${CMAKE_CURRENT_BINARY_DIR}/hiredis-config-version.cmake\"\n                                 COMPATIBILITY SameMajorVersion)\nconfigure_package_config_file(hiredis-config.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/hiredis-config.cmake\n                              INSTALL_DESTINATION ${CMAKE_CONF_INSTALL_DIR}\n                              PATH_VARS INCLUDE_INSTALL_DIR)\n\nINSTALL(EXPORT hiredis-targets\n        FILE hiredis-targets.cmake\n        NAMESPACE hiredis::\n        DESTINATION ${CMAKE_CONF_INSTALL_DIR})\n\nINSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/hiredis-config.cmake\n              ${CMAKE_CURRENT_BINARY_DIR}/hiredis-config-version.cmake\n        DESTINATION ${CMAKE_CONF_INSTALL_DIR})\n\n\nIF(ENABLE_SSL)\n    IF (NOT OPENSSL_ROOT_DIR)\n        IF (APPLE)\n            SET(OPENSSL_ROOT_DIR \"/usr/local/opt/openssl\")\n        ENDIF()\n    ENDIF()\n    FIND_PACKAGE(OpenSSL REQUIRED)\n    SET(hiredis_ssl_sources\n        ssl.c)\n    ADD_LIBRARY(hiredis_ssl ${hiredis_ssl_sources})\n    ADD_LIBRARY(hiredis::hiredis_ssl ALIAS hiredis_ssl)\n\n    IF (APPLE AND BUILD_SHARED_LIBS)\n        SET_PROPERTY(TARGET hiredis_ssl PROPERTY LINK_FLAGS \"-Wl,-undefined -Wl,dynamic_lookup\")\n    ENDIF()\n\n    SET_TARGET_PROPERTIES(hiredis_ssl\n        PROPERTIES\n        WINDOWS_EXPORT_ALL_SYMBOLS TRUE\n        VERSION \"${VERSION}\"\n        SOVERSION \"${HIREDIS_MAJOR}\")\n    IF(MSVC)\n        SET_TARGET_PROPERTIES(hiredis_ssl\n            PROPERTIES COMPILE_FLAGS /Z7)\n    ENDIF()\n    TARGET_LINK_LIBRARIES(hiredis_ssl PRIVATE OpenSSL::SSL)\n    IF(WIN32)\n        TARGET_LINK_LIBRARIES(hiredis_ssl PRIVATE hiredis)\n    ENDIF()\n    CONFIGURE_FILE(hiredis_ssl.pc.in hiredis_ssl.pc @ONLY)\n\n    INSTALL(TARGETS hiredis_ssl\n        EXPORT hiredis_ssl-targets\n        RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}\n        LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}\n        ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR})\n\n    if (MSVC AND BUILD_SHARED_LIBS)\n        INSTALL(FILES $<TARGET_PDB_FILE:hiredis_ssl>\n            DESTINATION ${CMAKE_INSTALL_BINDIR}\n            CONFIGURATIONS Debug RelWithDebInfo)\n    endif()\n\n    INSTALL(FILES hiredis_ssl.h\n        DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/hiredis)\n\n    INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/hiredis_ssl.pc\n        DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig)\n\n    export(EXPORT hiredis_ssl-targets\n           FILE \"${CMAKE_CURRENT_BINARY_DIR}/hiredis_ssl-targets.cmake\"\n           NAMESPACE hiredis::)\n\n    if(WIN32)\n        SET(CMAKE_CONF_INSTALL_DIR share/hiredis_ssl)\n    else()\n        SET(CMAKE_CONF_INSTALL_DIR ${CMAKE_INSTALL_LIBDIR}/cmake/hiredis_ssl)\n    endif()\n    configure_package_config_file(hiredis_ssl-config.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/hiredis_ssl-config.cmake\n                                  INSTALL_DESTINATION ${CMAKE_CONF_INSTALL_DIR}\n                                  PATH_VARS INCLUDE_INSTALL_DIR)\n\n    INSTALL(EXPORT hiredis_ssl-targets\n        FILE hiredis_ssl-targets.cmake\n        NAMESPACE hiredis::\n        DESTINATION ${CMAKE_CONF_INSTALL_DIR})\n\n    INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/hiredis_ssl-config.cmake\n        DESTINATION ${CMAKE_CONF_INSTALL_DIR})\nENDIF()\n\nIF(NOT DISABLE_TESTS)\n    ENABLE_TESTING()\n    ADD_EXECUTABLE(hiredis-test test.c)\n    TARGET_LINK_LIBRARIES(hiredis-test hiredis)\n    IF(ENABLE_SSL_TESTS)\n        ADD_DEFINITIONS(-DHIREDIS_TEST_SSL=1)\n        TARGET_LINK_LIBRARIES(hiredis-test hiredis_ssl)\n    ENDIF()\n    IF(ENABLE_ASYNC_TESTS)\n        ADD_DEFINITIONS(-DHIREDIS_TEST_ASYNC=1)\n        TARGET_LINK_LIBRARIES(hiredis-test event)\n    ENDIF()\n    ADD_TEST(NAME hiredis-test\n        COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/test.sh)\nENDIF()\n\n# Add examples\nIF(ENABLE_EXAMPLES)\n    ADD_SUBDIRECTORY(examples)\nENDIF(ENABLE_EXAMPLES)\n"
  },
  {
    "path": "COPYING",
    "content": "Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com>\nCopyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com>\n\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n\n* Redistributions of source code must retain the above copyright notice,\n  this list of conditions and the following disclaimer.\n\n* Redistributions in binary form must reproduce the above copyright notice,\n  this list of conditions and the following disclaimer in the documentation\n  and/or other materials provided with the distribution.\n\n* Neither the name of Redis nor the names of its contributors may be used\n  to endorse or promote products derived from this software without specific\n  prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\nANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\nWARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR\nANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\nLOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON\nANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\nSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
  },
  {
    "path": "Makefile",
    "content": "# Hiredis Makefile\n# Copyright (C) 2010-2011 Salvatore Sanfilippo <antirez at gmail dot com>\n# Copyright (C) 2010-2011 Pieter Noordhuis <pcnoordhuis at gmail dot com>\n# This file is released under the BSD license, see the COPYING file\n\nOBJ=alloc.o net.o hiredis.o sds.o async.o read.o sockcompat.o\nEXAMPLES=hiredis-example hiredis-example-libevent hiredis-example-libev hiredis-example-glib hiredis-example-push hiredis-example-poll\nTESTS=hiredis-test\nLIBNAME=libhiredis\nPKGCONFNAME=hiredis.pc\n\nHIREDIS_MAJOR=$(shell grep HIREDIS_MAJOR hiredis.h | awk '{print $$3}')\nHIREDIS_MINOR=$(shell grep HIREDIS_MINOR hiredis.h | awk '{print $$3}')\nHIREDIS_PATCH=$(shell grep HIREDIS_PATCH hiredis.h | awk '{print $$3}')\nHIREDIS_SONAME=$(shell grep HIREDIS_SONAME hiredis.h | awk '{print $$3}')\n\n# Installation related variables and target\nPREFIX?=/usr/local\nINCLUDE_PATH?=include/hiredis\nLIBRARY_PATH?=lib\nPKGCONF_PATH?=pkgconfig\nINSTALL_INCLUDE_PATH= $(DESTDIR)$(PREFIX)/$(INCLUDE_PATH)\nINSTALL_LIBRARY_PATH= $(DESTDIR)$(PREFIX)/$(LIBRARY_PATH)\nINSTALL_PKGCONF_PATH= $(INSTALL_LIBRARY_PATH)/$(PKGCONF_PATH)\n\n# redis-server configuration used for testing\nREDIS_PORT=56379\nREDIS_SERVER=redis-server\ndefine REDIS_TEST_CONFIG\n\tdaemonize yes\n\tpidfile /tmp/hiredis-test-redis.pid\n\tport $(REDIS_PORT)\n\tbind 127.0.0.1\n\tunixsocket /tmp/hiredis-test-redis.sock\nendef\nexport REDIS_TEST_CONFIG\n\n# Fallback to gcc when $CC is not in $PATH.\nCC:=$(shell sh -c 'type $${CC%% *} >/dev/null 2>/dev/null && echo $(CC) || echo gcc')\nCXX:=$(shell sh -c 'type $${CXX%% *} >/dev/null 2>/dev/null && echo $(CXX) || echo g++')\nOPTIMIZATION?=-O3\nWARNINGS=-Wall -Wextra -Wstrict-prototypes -Wwrite-strings -Wno-missing-field-initializers\nUSE_WERROR?=1\nifeq ($(USE_WERROR),1)\n  WARNINGS+=-Werror\nendif\nDEBUG_FLAGS?= -g -ggdb\nREAL_CFLAGS=$(OPTIMIZATION) -fPIC $(CPPFLAGS) $(CFLAGS) $(WARNINGS) $(DEBUG_FLAGS) $(PLATFORM_FLAGS)\nREAL_LDFLAGS=$(LDFLAGS)\n\nDYLIBSUFFIX=so\nSTLIBSUFFIX=a\nDYLIB_MINOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_SONAME)\nDYLIB_MAJOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_MAJOR)\nDYLIBNAME=$(LIBNAME).$(DYLIBSUFFIX)\n\nDYLIB_MAKE_CMD=$(CC) $(PLATFORM_FLAGS) -shared -Wl,-soname,$(DYLIB_MINOR_NAME)\nSTLIBNAME=$(LIBNAME).$(STLIBSUFFIX)\nSTLIB_MAKE_CMD=$(AR) rcs\n\n#################### SSL variables start ####################\nSSL_OBJ=ssl.o\nSSL_LIBNAME=libhiredis_ssl\nSSL_PKGCONFNAME=hiredis_ssl.pc\nSSL_INSTALLNAME=install-ssl\nSSL_DYLIB_MINOR_NAME=$(SSL_LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_SONAME)\nSSL_DYLIB_MAJOR_NAME=$(SSL_LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_MAJOR)\nSSL_DYLIBNAME=$(SSL_LIBNAME).$(DYLIBSUFFIX)\nSSL_STLIBNAME=$(SSL_LIBNAME).$(STLIBSUFFIX)\nSSL_DYLIB_MAKE_CMD=$(CC) $(PLATFORM_FLAGS) -shared -Wl,-soname,$(SSL_DYLIB_MINOR_NAME)\n\nUSE_SSL?=0\nifeq ($(USE_SSL),1)\n  # This is required for test.c only\n  CFLAGS+=-DHIREDIS_TEST_SSL\n  EXAMPLES+=hiredis-example-ssl hiredis-example-libevent-ssl\n  SSL_STLIB=$(SSL_STLIBNAME)\n  SSL_DYLIB=$(SSL_DYLIBNAME)\n  SSL_PKGCONF=$(SSL_PKGCONFNAME)\n  SSL_INSTALL=$(SSL_INSTALLNAME)\nelse\n  SSL_STLIB=\n  SSL_DYLIB=\n  SSL_PKGCONF=\n  SSL_INSTALL=\nendif\n##################### SSL variables end #####################\n\n\n# Platform-specific overrides\nuname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not')\n\n# This is required for test.c only\nifeq ($(TEST_ASYNC),1)\n  export CFLAGS+=-DHIREDIS_TEST_ASYNC\nendif\n\nifeq ($(USE_SSL),1)\n  ifndef OPENSSL_PREFIX\n    ifeq ($(uname_S),Darwin)\n      SEARCH_PATH1=/opt/homebrew/opt/openssl\n      SEARCH_PATH2=/usr/local/opt/openssl\n\n      ifneq ($(wildcard $(SEARCH_PATH1)),)\n        OPENSSL_PREFIX=$(SEARCH_PATH1)\n      else ifneq ($(wildcard $(SEARCH_PATH2)),)\n        OPENSSL_PREFIX=$(SEARCH_PATH2)\n      endif\n    endif\n  endif\n\n  ifdef OPENSSL_PREFIX\n    CFLAGS+=-I$(OPENSSL_PREFIX)/include\n    SSL_LDFLAGS+=-L$(OPENSSL_PREFIX)/lib\n  endif\n\n  SSL_LDFLAGS+=-lssl -lcrypto\nendif\n\nifeq ($(uname_S),FreeBSD)\n  LDFLAGS+=-lm\n  IS_GCC=$(shell sh -c '$(CC) --version 2>/dev/null |egrep -i -c \"gcc\"')\n  ifeq ($(IS_GCC),1)\n    REAL_CFLAGS+=-pedantic\n  endif\nelse\n  REAL_CFLAGS+=-pedantic\nendif\n\nifeq ($(uname_S),SunOS)\n  IS_SUN_CC=$(shell sh -c '$(CC) -V 2>&1 |egrep -i -c \"sun|studio\"')\n  ifeq ($(IS_SUN_CC),1)\n    SUN_SHARED_FLAG=-G\n  else\n    SUN_SHARED_FLAG=-shared\n  endif\n  REAL_LDFLAGS+= -ldl -lnsl -lsocket\n  DYLIB_MAKE_CMD=$(CC) $(SUN_SHARED_FLAG) -o $(DYLIBNAME) -h $(DYLIB_MINOR_NAME) $(LDFLAGS)\n  SSL_DYLIB_MAKE_CMD=$(CC) $(SUN_SHARED_FLAG) -o $(SSL_DYLIBNAME) -h $(SSL_DYLIB_MINOR_NAME) $(LDFLAGS) $(SSL_LDFLAGS)\nendif\nifeq ($(uname_S),Darwin)\n  DYLIBSUFFIX=dylib\n  DYLIB_MINOR_NAME=$(LIBNAME).$(HIREDIS_SONAME).$(DYLIBSUFFIX)\n  DYLIB_MAJOR_NAME=$(LIBNAME).$(HIREDIS_MAJOR).$(DYLIBSUFFIX)\n  DYLIB_MAKE_CMD=$(CC) -dynamiclib -Wl,-install_name,$(PREFIX)/$(LIBRARY_PATH)/$(DYLIB_MINOR_NAME) -o $(DYLIBNAME) $(LDFLAGS)\n  SSL_DYLIB_MINOR_NAME=$(SSL_LIBNAME).$(HIREDIS_SONAME).$(DYLIBSUFFIX)\n  SSL_DYLIB_MAJOR_NAME=$(SSL_LIBNAME).$(HIREDIS_MAJOR).$(DYLIBSUFFIX)\n  SSL_DYLIB_MAKE_CMD=$(CC) -dynamiclib -Wl,-install_name,$(PREFIX)/$(LIBRARY_PATH)/$(SSL_DYLIB_MINOR_NAME) -o $(SSL_DYLIBNAME) $(LDFLAGS) $(SSL_LDFLAGS)\n  DYLIB_PLUGIN=-Wl,-undefined -Wl,dynamic_lookup\nendif\n\nall: dynamic static hiredis-test pkgconfig\n\ndynamic: $(DYLIBNAME) $(SSL_DYLIB)\n\nstatic: $(STLIBNAME) $(SSL_STLIB)\n\npkgconfig: $(PKGCONFNAME) $(SSL_PKGCONF)\n\n# Deps (use make dep to generate this)\nalloc.o: alloc.c fmacros.h alloc.h\nasync.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\ndict.o: dict.c fmacros.h alloc.h dict.h\nhiredis.o: hiredis.c fmacros.h hiredis.h read.h sds.h alloc.h net.h async.h win32.h\nnet.o: net.c fmacros.h net.h hiredis.h read.h sds.h alloc.h sockcompat.h win32.h\nread.o: read.c fmacros.h alloc.h read.h sds.h win32.h\nsds.o: sds.c sds.h sdsalloc.h alloc.h\nsockcompat.o: sockcompat.c sockcompat.h\ntest.o: test.c fmacros.h hiredis.h read.h sds.h alloc.h net.h sockcompat.h win32.h\n\n$(DYLIBNAME): $(OBJ)\n\t$(DYLIB_MAKE_CMD) -o $(DYLIBNAME) $(OBJ) $(REAL_LDFLAGS)\n\n$(STLIBNAME): $(OBJ)\n\t$(STLIB_MAKE_CMD) $(STLIBNAME) $(OBJ)\n\n#################### SSL building rules start ####################\n$(SSL_DYLIBNAME): $(SSL_OBJ)\n\t$(SSL_DYLIB_MAKE_CMD) $(DYLIB_PLUGIN) -o $(SSL_DYLIBNAME) $(SSL_OBJ) $(REAL_LDFLAGS) $(LDFLAGS) $(SSL_LDFLAGS)\n\n$(SSL_STLIBNAME): $(SSL_OBJ)\n\t$(STLIB_MAKE_CMD) $(SSL_STLIBNAME) $(SSL_OBJ)\n\n$(SSL_OBJ): ssl.c hiredis.h read.h sds.h alloc.h async.h win32.h async_private.h\n#################### SSL building rules end ####################\n\n# Binaries:\nhiredis-example-libevent: examples/example-libevent.c adapters/libevent.h $(STLIBNAME)\n\t$(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< -levent $(STLIBNAME) $(REAL_LDFLAGS)\n\nhiredis-example-libevent-ssl: examples/example-libevent-ssl.c adapters/libevent.h $(STLIBNAME) $(SSL_STLIBNAME)\n\t$(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< -levent $(STLIBNAME) $(SSL_STLIBNAME) $(REAL_LDFLAGS) $(SSL_LDFLAGS)\n\nhiredis-example-libev: examples/example-libev.c adapters/libev.h $(STLIBNAME)\n\t$(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< -lev $(STLIBNAME) $(REAL_LDFLAGS)\n\nhiredis-example-libhv: examples/example-libhv.c adapters/libhv.h $(STLIBNAME)\n\t$(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< -lhv $(STLIBNAME) $(REAL_LDFLAGS)\n\nhiredis-example-glib: examples/example-glib.c adapters/glib.h $(STLIBNAME)\n\t$(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< $(shell pkg-config --cflags --libs glib-2.0) $(STLIBNAME) $(REAL_LDFLAGS)\n\nhiredis-example-ivykis: examples/example-ivykis.c adapters/ivykis.h $(STLIBNAME)\n\t$(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< -livykis $(STLIBNAME) $(REAL_LDFLAGS)\n\nhiredis-example-macosx: examples/example-macosx.c adapters/macosx.h $(STLIBNAME)\n\t$(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< -framework CoreFoundation $(STLIBNAME) $(REAL_LDFLAGS)\n\nhiredis-example-ssl: examples/example-ssl.c $(STLIBNAME) $(SSL_STLIBNAME)\n\t$(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< $(STLIBNAME) $(SSL_STLIBNAME) $(REAL_LDFLAGS) $(SSL_LDFLAGS)\n\nhiredis-example-poll: examples/example-poll.c adapters/poll.h $(STLIBNAME)\n\t$(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< $(STLIBNAME) $(REAL_LDFLAGS)\n\nifndef AE_DIR\nhiredis-example-ae:\n\t@echo \"Please specify AE_DIR (e.g. <redis repository>/src)\"\n\t@false\nelse\nhiredis-example-ae: examples/example-ae.c adapters/ae.h $(STLIBNAME)\n\t$(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)\nendif\n\nifndef LIBUV_DIR\n# dynamic link libuv.so\nhiredis-example-libuv: examples/example-libuv.c adapters/libuv.h $(STLIBNAME)\n\t$(CC) -o examples/$@ $(REAL_CFLAGS) -I. -I$(LIBUV_DIR)/include $< -luv -lpthread -lrt $(STLIBNAME) $(REAL_LDFLAGS)\nelse\n# use user provided static lib\nhiredis-example-libuv: examples/example-libuv.c adapters/libuv.h $(STLIBNAME)\n\t$(CC) -o examples/$@ $(REAL_CFLAGS) -I. -I$(LIBUV_DIR)/include $< $(LIBUV_DIR)/.libs/libuv.a -lpthread -lrt $(STLIBNAME) $(REAL_LDFLAGS)\nendif\n\nifeq ($(and $(QT_MOC),$(QT_INCLUDE_DIR),$(QT_LIBRARY_DIR)),)\nhiredis-example-qt:\n\t@echo \"Please specify QT_MOC, QT_INCLUDE_DIR AND QT_LIBRARY_DIR\"\n\t@false\nelse\nhiredis-example-qt: examples/example-qt.cpp adapters/qt.h $(STLIBNAME)\n\t$(QT_MOC) adapters/qt.h -I. -I$(QT_INCLUDE_DIR) -I$(QT_INCLUDE_DIR)/QtCore | \\\n\t    $(CXX) -x c++ -o qt-adapter-moc.o -c - $(REAL_CFLAGS) -I. -I$(QT_INCLUDE_DIR) -I$(QT_INCLUDE_DIR)/QtCore\n\t$(QT_MOC) examples/example-qt.h -I. -I$(QT_INCLUDE_DIR) -I$(QT_INCLUDE_DIR)/QtCore | \\\n\t    $(CXX) -x c++ -o qt-example-moc.o -c - $(REAL_CFLAGS) -I. -I$(QT_INCLUDE_DIR) -I$(QT_INCLUDE_DIR)/QtCore\n\t$(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\nendif\n\nhiredis-example: examples/example.c $(STLIBNAME)\n\t$(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< $(STLIBNAME) $(REAL_LDFLAGS)\n\nhiredis-example-push: examples/example-push.c $(STLIBNAME)\n\t$(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< $(STLIBNAME) $(REAL_LDFLAGS)\n\nexamples: $(EXAMPLES)\n\nTEST_LIBS = $(STLIBNAME) $(SSL_STLIB)\nTEST_LDFLAGS = $(SSL_LDFLAGS)\nifeq ($(USE_SSL),1)\n  TEST_LDFLAGS += -pthread\nendif\nifeq ($(TEST_ASYNC),1)\n    TEST_LDFLAGS += -levent\nendif\n\nhiredis-test: test.o $(TEST_LIBS)\n\t$(CC) -o $@ $(REAL_CFLAGS) -I. $^ $(REAL_LDFLAGS) $(TEST_LDFLAGS)\n\nhiredis-%: %.o $(STLIBNAME)\n\t$(CC) $(REAL_CFLAGS) -o $@ $< $(TEST_LIBS) $(REAL_LDFLAGS)\n\ntest: hiredis-test\n\t./hiredis-test\n\ncheck: hiredis-test\n\tTEST_SSL=$(USE_SSL) ./test.sh\n\n.c.o:\n\t$(CC) -std=c99 -c $(REAL_CFLAGS) $<\n\nclean:\n\trm -rf $(DYLIBNAME) $(STLIBNAME) $(SSL_DYLIBNAME) $(SSL_STLIBNAME) $(TESTS) $(PKGCONFNAME) examples/hiredis-example* *.o *.gcda *.gcno *.gcov\n\ndep:\n\t$(CC) $(CPPFLAGS) $(CFLAGS) -MM *.c\n\nINSTALL?= cp -pPR\n\n$(PKGCONFNAME): hiredis.h\n\t@echo \"Generating $@ for pkgconfig...\"\n\t@echo prefix=$(PREFIX) > $@\n\t@echo exec_prefix=\\$${prefix} >> $@\n\t@echo libdir=$(PREFIX)/$(LIBRARY_PATH) >> $@\n\t@echo includedir=$(PREFIX)/include >> $@\n\t@echo pkgincludedir=$(PREFIX)/$(INCLUDE_PATH) >> $@\n\t@echo >> $@\n\t@echo Name: hiredis >> $@\n\t@echo Description: Minimalistic C client library for Redis. >> $@\n\t@echo Version: $(HIREDIS_MAJOR).$(HIREDIS_MINOR).$(HIREDIS_PATCH) >> $@\n\t@echo Libs: -L\\$${libdir} -lhiredis >> $@\n\t@echo Cflags: -I\\$${pkgincludedir} -I\\$${includedir} -D_FILE_OFFSET_BITS=64 >> $@\n\n$(SSL_PKGCONFNAME): hiredis_ssl.h\n\t@echo \"Generating $@ for pkgconfig...\"\n\t@echo prefix=$(PREFIX) > $@\n\t@echo exec_prefix=\\$${prefix} >> $@\n\t@echo libdir=$(PREFIX)/$(LIBRARY_PATH) >> $@\n\t@echo includedir=$(PREFIX)/include >> $@\n\t@echo pkgincludedir=$(PREFIX)/$(INCLUDE_PATH) >> $@\n\t@echo >> $@\n\t@echo Name: hiredis_ssl >> $@\n\t@echo Description: SSL Support for hiredis. >> $@\n\t@echo Version: $(HIREDIS_MAJOR).$(HIREDIS_MINOR).$(HIREDIS_PATCH) >> $@\n\t@echo Requires: hiredis >> $@\n\t@echo Libs: -L\\$${libdir} -lhiredis_ssl >> $@\n\t@echo Libs.private: -lssl -lcrypto >> $@\n\ninstall: $(DYLIBNAME) $(STLIBNAME) $(PKGCONFNAME) $(SSL_INSTALL)\n\tmkdir -p $(INSTALL_INCLUDE_PATH) $(INSTALL_INCLUDE_PATH)/adapters $(INSTALL_LIBRARY_PATH)\n\t$(INSTALL) hiredis.h async.h read.h sds.h alloc.h sockcompat.h $(INSTALL_INCLUDE_PATH)\n\t$(INSTALL) adapters/*.h $(INSTALL_INCLUDE_PATH)/adapters\n\t$(INSTALL) $(DYLIBNAME) $(INSTALL_LIBRARY_PATH)/$(DYLIB_MINOR_NAME)\n\tcd $(INSTALL_LIBRARY_PATH) && ln -sf $(DYLIB_MINOR_NAME) $(DYLIBNAME) && ln -sf $(DYLIB_MINOR_NAME) $(DYLIB_MAJOR_NAME)\n\t$(INSTALL) $(STLIBNAME) $(INSTALL_LIBRARY_PATH)\n\tmkdir -p $(INSTALL_PKGCONF_PATH)\n\t$(INSTALL) $(PKGCONFNAME) $(INSTALL_PKGCONF_PATH)\n\ninstall-ssl: $(SSL_DYLIBNAME) $(SSL_STLIBNAME) $(SSL_PKGCONFNAME)\n\tmkdir -p $(INSTALL_INCLUDE_PATH) $(INSTALL_LIBRARY_PATH)\n\t$(INSTALL) hiredis_ssl.h $(INSTALL_INCLUDE_PATH)\n\t$(INSTALL) $(SSL_DYLIBNAME) $(INSTALL_LIBRARY_PATH)/$(SSL_DYLIB_MINOR_NAME)\n\tcd $(INSTALL_LIBRARY_PATH) && ln -sf $(SSL_DYLIB_MINOR_NAME) $(SSL_DYLIBNAME) && ln -sf $(SSL_DYLIB_MINOR_NAME) $(SSL_DYLIB_MAJOR_NAME)\n\t$(INSTALL) $(SSL_STLIBNAME) $(INSTALL_LIBRARY_PATH)\n\tmkdir -p $(INSTALL_PKGCONF_PATH)\n\t$(INSTALL) $(SSL_PKGCONFNAME) $(INSTALL_PKGCONF_PATH)\n\n32bit:\n\t@echo \"\"\n\t@echo \"WARNING: if this fails under Linux you probably need to install libc6-dev-i386\"\n\t@echo \"\"\n\t$(MAKE) CFLAGS=\"-m32\" LDFLAGS=\"-m32\"\n\n32bit-vars:\n\t$(eval CFLAGS=-m32)\n\t$(eval LDFLAGS=-m32)\n\ngprof:\n\t$(MAKE) CFLAGS=\"-pg\" LDFLAGS=\"-pg\"\n\ngcov:\n\t$(MAKE) CFLAGS+=\"-fprofile-arcs -ftest-coverage\" LDFLAGS=\"-fprofile-arcs\"\n\ncoverage: gcov\n\tmake check\n\tmkdir -p tmp/lcov\n\tlcov -d . -c --exclude '/usr*' -o tmp/lcov/hiredis.info\n\tlcov -q -l tmp/lcov/hiredis.info\n\tgenhtml --legend -q -o tmp/lcov/report tmp/lcov/hiredis.info\n\nnoopt:\n\t$(MAKE) OPTIMIZATION=\"\"\n\n.PHONY: all test check clean dep install 32bit 32bit-vars gprof gcov noopt\n"
  },
  {
    "path": "README.md",
    "content": "\n[![Build Status](https://github.com/redis/hiredis/actions/workflows/build.yml/badge.svg)](https://github.com/redis/hiredis/actions/workflows/build.yml)\n\n**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)).**\n\n# HIREDIS\n\nHiredis is a minimalistic C client library for the [Redis](https://redis.io/) database.\n\nIt is minimalistic because it just adds minimal support for the protocol, but\nat the same time it uses a high level printf-alike API in order to make it\nmuch higher level than otherwise suggested by its minimal code base and the\nlack of explicit bindings for every Redis command.\n\nApart from supporting sending commands and receiving replies, it comes with\na reply parser that is decoupled from the I/O layer. It\nis a stream parser designed for easy reusability, which can for instance be used\nin higher level language bindings for efficient reply parsing.\n\nHiredis only supports the binary-safe Redis protocol, so you can use it with any\nRedis version >= 1.2.0.\n\nThe library comes with multiple APIs. There is the\n*synchronous API*, the *asynchronous API* and the *reply parsing API*.\n\n## Upgrading to > 1.2.0 (**PRERELEASE**)\n\n* After v1.2.0 we modified how we invoke `poll(2)` to wait for connections to complete, such that we will now retry\n  the call if it is interrupted by a signal until:\n\n  a) The connection succeeds or fails.\n  b) The overall connection timeout is reached.\n\n  In previous versions, an interrupted `poll(2)` call would cause the connection to fail\n  with `c->err` set to `REDIS_ERR_IO` and `c->errstr` set to `poll(2): Interrupted system call`.\n\n## Upgrading to `1.1.0`\n\nAlmost all users will simply need to recompile their applications against the newer version of hiredis.\n\n**NOTE**:  Hiredis can now return `nan` in addition to `-inf` and `inf` in a `REDIS_REPLY_DOUBLE`.\n           Applications that deal with `RESP3` doubles should make sure to account for this.\n\n## Upgrading to `1.0.2`\n\n<span style=\"color:red\">NOTE:  v1.0.1 erroneously bumped SONAME, which is why it is skipped here.</span>\n\nVersion 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.\n\n## Upgrading to `1.0.0`\n\nVersion 1.0.0 marks the first stable release of Hiredis.\nIt includes some minor breaking changes, mostly to make the exposed API more uniform and self-explanatory.\nIt also bundles the updated `sds` library, to sync up with upstream and Redis.\nFor code changes see the [Changelog](CHANGELOG.md).\n\n_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._\n\n## IMPORTANT:  Breaking changes from `0.14.1` -> `1.0.0`\n\n* `redisContext` has two additional members (`free_privdata`, and `privctx`).\n* `redisOptions.timeout` has been renamed to `redisOptions.connect_timeout`, and we've added `redisOptions.command_timeout`.\n* `redisReplyObjectFunctions.createArray` now takes `size_t` instead of `int` for its length parameter.\n\n## IMPORTANT:  Breaking changes when upgrading from 0.13.x -> 0.14.x\n\nBulk and multi-bulk lengths less than -1 or greater than `LLONG_MAX` are now\nprotocol errors. This is consistent with the RESP specification. On 32-bit\nplatforms, the upper bound is lowered to `SIZE_MAX`.\n\nChange `redisReply.len` to `size_t`, as it denotes the the size of a string\n\nUser code should compare this to `size_t` values as well.  If it was used to\ncompare to other values, casting might be necessary or can be removed, if\ncasting was applied before.\n\n## Upgrading from `<0.9.0`\n\nVersion 0.9.0 is a major overhaul of hiredis in every aspect. However, upgrading existing\ncode using hiredis should not be a big pain. The key thing to keep in mind when\nupgrading is that hiredis >= 0.9.0 uses a `redisContext*` to keep state, in contrast to\nthe stateless 0.0.1 that only has a file descriptor to work with.\n\n## Synchronous API\n\nTo consume the synchronous API, there are only a few function calls that need to be introduced:\n\n```c\nredisContext *redisConnect(const char *ip, int port);\nvoid *redisCommand(redisContext *c, const char *format, ...);\nvoid freeReplyObject(void *reply);\n```\n\n### Connecting\n\nThe function `redisConnect` is used to create a so-called `redisContext`. The\ncontext is where Hiredis holds state for a connection. The `redisContext`\nstruct has an integer `err` field that is non-zero when the connection is in\nan error state. The field `errstr` will contain a string with a description of\nthe error. More information on errors can be found in the **Errors** section.\nAfter trying to connect to Redis using `redisConnect` you should\ncheck the `err` field to see if establishing the connection was successful:\n\n```c\nredisContext *c = redisConnect(\"127.0.0.1\", 6379);\nif (c == NULL || c->err) {\n    if (c) {\n        printf(\"Error: %s\\n\", c->errstr);\n        // handle error\n    } else {\n        printf(\"Can't allocate redis context\\n\");\n    }\n}\n```\n\nOne can also use `redisConnectWithOptions` which takes a `redisOptions` argument\nthat can be configured with endpoint information as well as many different flags\nto change how the `redisContext` will be configured.\n\n```c\nredisOptions opt = {0};\n\n/* One can set the endpoint with one of our helper macros */\nif (tcp) {\n    REDIS_OPTIONS_SET_TCP(&opt, \"localhost\", 6379);\n} else {\n    REDIS_OPTIONS_SET_UNIX(&opt, \"/tmp/redis.sock\");\n}\n\n/* And privdata can be specified with another helper */\nREDIS_OPTIONS_SET_PRIVDATA(&opt, myPrivData, myPrivDataDtor);\n\n/* Finally various options may be set via the `options` member, as described below */\nopt->options |= REDIS_OPT_PREFER_IPV4;\n```\n\nIf a connection is lost, `int redisReconnect(redisContext *c)` can be used to restore the connection using the same endpoint and options as the given context.\n\n### Configurable redisOptions flags\n\nThere are several flags you may set in the `redisOptions` struct to change default behavior.  You can specify the flags via the `redisOptions->options` member.\n\n| Flag | Description  |\n| --- | --- |\n| REDIS\\_OPT\\_NONBLOCK | Tells hiredis to make a non-blocking connection. |\n| REDIS\\_OPT\\_REUSEADDR | Tells hiredis to set the [SO_REUSEADDR](https://man7.org/linux/man-pages/man7/socket.7.html) socket option |\n| REDIS\\_OPT\\_PREFER\\_IPV4<br>REDIS\\_OPT\\_PREFER_IPV6<br>REDIS\\_OPT\\_PREFER\\_IP\\_UNSPEC | Informs hiredis to either prefer IPv4 or IPv6 when invoking [getaddrinfo](https://man7.org/linux/man-pages/man3/gai_strerror.3.html).  `REDIS_OPT_PREFER_IP_UNSPEC` will cause hiredis to specify `AF_UNSPEC` in the getaddrinfo call, which means both IPv4 and IPv6 addresses will be searched simultaneously.<br>Hiredis prefers IPv4 by default. |\n| REDIS\\_OPT\\_NO\\_PUSH\\_AUTOFREE | Tells hiredis to not install the default RESP3 PUSH handler (which just intercepts and frees the replies).  This is useful in situations where you want to process these messages in-band. |\n| REDIS\\_OPT\\_NOAUTOFREEREPLIES | **ASYNC**: tells hiredis not to automatically invoke `freeReplyObject` after executing the reply callback. |\n| REDIS\\_OPT\\_NOAUTOFREE | **ASYNC**: Tells hiredis not to automatically free the `redisAsyncContext` on connection/communication failure, but only if the user makes an explicit call to `redisAsyncDisconnect` or `redisAsyncFree` |\n\n*Note: A `redisContext` is not thread-safe.*\n\n### Other configuration using socket options\n\nThe following socket options are applied directly to the underlying socket.\nThe values are not stored in the `redisContext`, so they are not automatically applied when reconnecting using `redisReconnect()`.\nThese functions return `REDIS_OK` on success.\nOn failure, `REDIS_ERR` is returned and the underlying connection is closed.\n\nTo configure these for an asynchronous context (see *Asynchronous API* below), use `ac->c` to get the redisContext out of an asyncRedisContext.\n\n```C\nint redisEnableKeepAlive(redisContext *c);\nint redisEnableKeepAliveWithInterval(redisContext *c, int interval);\n```\n\nEnables TCP keepalive by setting the following socket options (with some variations depending on OS):\n\n* `SO_KEEPALIVE`;\n* `TCP_KEEPALIVE` or `TCP_KEEPIDLE`, value configurable using the `interval` parameter, default 15 seconds;\n* `TCP_KEEPINTVL` set to 1/3 of `interval`;\n* `TCP_KEEPCNT` set to 3.\n\n```C\nint redisSetTcpUserTimeout(redisContext *c, unsigned int timeout);\n```\n\nSet the `TCP_USER_TIMEOUT` Linux-specific socket option which is as described in the `tcp` man page:\n\n> When the value is greater than 0, it specifies the maximum amount of time in milliseconds that trans mitted data may remain unacknowledged before TCP will forcibly close the corresponding connection and return ETIMEDOUT to the application.\n> If the option value is specified as 0, TCP will use the system default.\n\n### Sending commands\n\nThere are several ways to issue commands to Redis. The first that will be introduced is\n`redisCommand`. This function takes a format similar to printf. In the simplest form,\nit is used like this:\n```c\nreply = redisCommand(context, \"SET foo bar\");\n```\n\nThe specifier `%s` interpolates a string in the command, and uses `strlen` to\ndetermine the length of the string:\n```c\nreply = redisCommand(context, \"SET foo %s\", value);\n```\nWhen you need to pass binary safe strings in a command, the `%b` specifier can be\nused. Together with a pointer to the string, it requires a `size_t` length argument\nof the string:\n```c\nreply = redisCommand(context, \"SET foo %b\", value, (size_t) valuelen);\n```\nInternally, Hiredis splits the command in different arguments and will\nconvert it to the protocol used to communicate with Redis.\nOne or more spaces separates arguments, so you can use the specifiers\nanywhere in an argument:\n```c\nreply = redisCommand(context, \"SET key:%s %s\", myid, value);\n```\n\n### Using replies\n\nThe return value of `redisCommand` holds a reply when the command was\nsuccessfully executed. When an error occurs, the return value is `NULL` and\nthe `err` field in the context will be set (see section on **Errors**).\nOnce an error is returned the context cannot be reused and you should set up\na new connection.\n\nThe standard replies that `redisCommand` are of the type `redisReply`. The\n`type` field in the `redisReply` should be used to test what kind of reply\nwas received:\n\n### RESP2\n\n* **`REDIS_REPLY_STATUS`**:\n    * The command replied with a status reply. The status string can be accessed using `reply->str`.\n      The length of this string can be accessed using `reply->len`.\n\n* **`REDIS_REPLY_ERROR`**:\n    *  The command replied with an error. The error string can be accessed identical to `REDIS_REPLY_STATUS`.\n\n* **`REDIS_REPLY_INTEGER`**:\n    * The command replied with an integer. The integer value can be accessed using the\n      `reply->integer` field of type `long long`.\n\n* **`REDIS_REPLY_NIL`**:\n    * The command replied with a **nil** object. There is no data to access.\n\n* **`REDIS_REPLY_STRING`**:\n    * A bulk (string) reply. The value of the reply can be accessed using `reply->str`.\n      The length of this string can be accessed using `reply->len`.\n\n* **`REDIS_REPLY_ARRAY`**:\n    * A multi bulk reply. The number of elements in the multi bulk reply is stored in\n      `reply->elements`. Every element in the multi bulk reply is a `redisReply` object as well\n      and can be accessed via `reply->element[..index..]`.\n      Redis may reply with nested arrays but this is fully supported.\n\n### RESP3\n\nHiredis 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)\n\n* **`REDIS_REPLY_DOUBLE`**:\n    * The command replied with a double-precision floating point number.\n      The value is stored as a string in the `str` member, and can be converted with `strtod` or similar.\n\n* **`REDIS_REPLY_BOOL`**:\n    * A boolean true/false reply.\n      The value is stored in the `integer` member and will be either `0` or `1`.\n\n* **`REDIS_REPLY_MAP`**:\n    * An array with the added invariant that there will always be an even number of elements.\n      The MAP is functionally equivalent to `REDIS_REPLY_ARRAY` except for the previously mentioned invariant.\n\n* **`REDIS_REPLY_SET`**:\n    * An array response where each entry is unique.\n      Like the MAP type, the data is identical to an array response except there are no duplicate values.\n\n* **`REDIS_REPLY_PUSH`**:\n    * An array that can be generated spontaneously by Redis.\n      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.\n\n* **`REDIS_REPLY_ATTR`**:\n    * An array structurally identical to a `MAP` but intended as meta-data about a reply.\n      _As of Redis 6.0.6 this reply type is not used in Redis_\n\n* **`REDIS_REPLY_BIGNUM`**:\n    * A string representing an arbitrarily large signed or unsigned integer value.\n      The number will be encoded as a string in the `str` member of `redisReply`.\n\n* **`REDIS_REPLY_VERB`**:\n    * A verbatim string, intended to be presented to the user without modification.\n      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).\n\nReplies should be freed using the `freeReplyObject()` function.\nNote that this function will take care of freeing sub-reply objects\ncontained in arrays and nested arrays, so there is no need for the user to\nfree the sub replies (it is actually harmful and will corrupt the memory).\n\n**Important:** the current version of hiredis (1.0.0) frees replies when the\nasynchronous API is used. This means you should not call `freeReplyObject` when\nyou use this API. The reply is cleaned up by hiredis _after_ the callback\nreturns.  We may introduce a flag to make this configurable in future versions of the library.\n\n### Cleaning up\n\nTo disconnect and free the context the following function can be used:\n```c\nvoid redisFree(redisContext *c);\n```\nThis function immediately closes the socket and then frees the allocations done in\ncreating the context.\n\n### Sending commands (continued)\n\nTogether with `redisCommand`, the function `redisCommandArgv` can be used to issue commands.\nIt has the following prototype:\n```c\nvoid *redisCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen);\n```\nIt takes the number of arguments `argc`, an array of strings `argv` and the lengths of the\narguments `argvlen`. For convenience, `argvlen` may be set to `NULL` and the function will\nuse `strlen(3)` on every argument to determine its length. Obviously, when any of the arguments\nneed to be binary safe, the entire array of lengths `argvlen` should be provided.\n\nThe return value has the same semantic as `redisCommand`.\n\n### Pipelining\n\nTo explain how Hiredis supports pipelining in a blocking connection, there needs to be\nunderstanding of the internal execution flow.\n\nWhen any of the functions in the `redisCommand` family is called, Hiredis first formats the\ncommand according to the Redis protocol. The formatted command is then put in the output buffer\nof the context. This output buffer is dynamic, so it can hold any number of commands.\nAfter the command is put in the output buffer, `redisGetReply` is called. This function has the\nfollowing two execution paths:\n\n1. The input buffer is non-empty:\n    * Try to parse a single reply from the input buffer and return it\n    * If no reply could be parsed, continue at *2*\n2. The input buffer is empty:\n    * Write the **entire** output buffer to the socket\n    * Read from the socket until a single reply could be parsed\n\nThe function `redisGetReply` is exported as part of the Hiredis API and can be used when a reply\nis expected on the socket. To pipeline commands, the only thing that needs to be done is\nfilling up the output buffer. For this cause, two commands can be used that are identical\nto the `redisCommand` family, apart from not returning a reply:\n```c\nvoid redisAppendCommand(redisContext *c, const char *format, ...);\nvoid redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen);\n```\nAfter calling either function one or more times, `redisGetReply` can be used to receive the\nsubsequent replies. The return value for this function is either `REDIS_OK` or `REDIS_ERR`, where\nthe latter means an error occurred while reading a reply. Just as with the other commands,\nthe `err` field in the context can be used to find out what the cause of this error is.\n\nThe following examples shows a simple pipeline (resulting in only a single call to `write(2)` and\na single call to `read(2)`):\n```c\nredisReply *reply;\nredisAppendCommand(context,\"SET foo bar\");\nredisAppendCommand(context,\"GET foo\");\nredisGetReply(context,(void**)&reply); // reply for SET\nfreeReplyObject(reply);\nredisGetReply(context,(void**)&reply); // reply for GET\nfreeReplyObject(reply);\n```\nThis API can also be used to implement a blocking subscriber:\n```c\nreply = redisCommand(context,\"SUBSCRIBE foo\");\nfreeReplyObject(reply);\nwhile(redisGetReply(context,(void *)&reply) == REDIS_OK) {\n    // consume message\n    freeReplyObject(reply);\n}\n```\n### Errors\n\nWhen a function call is not successful, depending on the function either `NULL` or `REDIS_ERR` is\nreturned. The `err` field inside the context will be non-zero and set to one of the\nfollowing constants:\n\n* **`REDIS_ERR_IO`**:\n    There was an I/O error while creating the connection, trying to write\n    to the socket or read from the socket. If you included `errno.h` in your\n    application, you can use the global `errno` variable to find out what is\n    wrong.\n\n* **`REDIS_ERR_EOF`**:\n    The server closed the connection which resulted in an empty read.\n\n* **`REDIS_ERR_PROTOCOL`**:\n    There was an error while parsing the protocol.\n\n* **`REDIS_ERR_OTHER`**:\n    Any other error. Currently, it is only used when a specified hostname to connect\n    to cannot be resolved.\n\nIn every case, the `errstr` field in the context will be set to hold a string representation\nof the error.\n\n## Asynchronous API\n\nHiredis comes with an asynchronous API that works easily with any event library.\nExamples are bundled that show using Hiredis with [libev](http://software.schmorp.de/pkg/libev.html)\nand [libevent](http://monkey.org/~provos/libevent/).\n\n### Connecting\n\nThe function `redisAsyncConnect` can be used to establish a non-blocking connection to\nRedis. It returns a pointer to the newly created `redisAsyncContext` struct. The `err` field\nshould be checked after creation to see if there were errors creating the connection.\nBecause the connection that will be created is non-blocking, the kernel is not able to\ninstantly return if the specified host and port is able to accept a connection.\nIn case of error, it is the caller's responsibility to free the context using `redisAsyncFree()`\n\n*Note: A `redisAsyncContext` is not thread-safe.*\n\nAn application function creating a connection might look like this:\n\n```c\nvoid appConnect(myAppData *appData)\n{\n    redisAsyncContext *c = redisAsyncConnect(\"127.0.0.1\", 6379);\n    if (c->err) {\n        printf(\"Error: %s\\n\", c->errstr);\n        // handle error\n        redisAsyncFree(c);\n        c = NULL;\n    } else {\n        appData->context = c;\n        appData->connecting = 1;\n        c->data = appData; /* store application pointer for the callbacks */\n        redisAsyncSetConnectCallback(c, appOnConnect);\n        redisAsyncSetDisconnectCallback(c, appOnDisconnect);\n    }\n}\n\n```\n\n\nThe asynchronous context _should_ hold a *connect* callback function that is called when the connection\nattempt completes, either successfully or with an error.\nIt _can_ also hold a *disconnect* callback function that is called when the\nconnection is disconnected (either because of an error or per user request). Both callbacks should\nhave the following prototype:\n```c\nvoid(const redisAsyncContext *c, int status);\n```\n\nOn a *connect*, the `status` argument is set to `REDIS_OK` if the connection attempt succeeded.  In this\ncase, the context is ready to accept commands.  If it is called with `REDIS_ERR` then the\nconnection attempt failed. The `err` field in the context can be accessed to find out the cause of the error.\nAfter a failed connection attempt, the context object is automatically freed by the library after calling\nthe connect callback.  This may be a good point to create a new context and retry the connection.\n\nOn a disconnect, the `status` argument is set to `REDIS_OK` when disconnection was initiated by the\nuser, or `REDIS_ERR` when the disconnection was caused by an error. When it is `REDIS_ERR`, the `err`\nfield in the context can be accessed to find out the cause of the error.\n\nThe context object is always freed after the disconnect callback fired. When a reconnect is needed,\nthe disconnect callback is a good point to do so.\n\nSetting the connect or disconnect callbacks can only be done once per context. For subsequent calls the\napi will return `REDIS_ERR`. The function to set the callbacks have the following prototype:\n```c\n/* Alternatively you can use redisAsyncSetConnectCallbackNC which will be passed a non-const\n   redisAsyncContext* on invocation (e.g. allowing writes to the privdata member). */\nint redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn);\nint redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn);\n```\n`ac->data` may be used to pass user data to both callbacks.  A typical implementation\nmight look something like this:\n```c\nvoid appOnConnect(redisAsyncContext *c, int status)\n{\n    myAppData *appData = (myAppData*)c->data; /* get my application specific context*/\n    appData->connecting = 0;\n    if (status == REDIS_OK) {\n        appData->connected = 1;\n    } else {\n        appData->connected = 0;\n        appData->err = c->err;\n        appData->context = NULL; /* avoid stale pointer when callback returns */\n    }\n    appAttemptReconnect();\n}\n\nvoid appOnDisconnect(redisAsyncContext *c, int status)\n{\n    myAppData *appData = (myAppData*)c->data; /* get my application specific context*/\n    appData->connected = 0;\n    appData->err = c->err;\n    appData->context = NULL; /* avoid stale pointer when callback returns */\n    if (status == REDIS_OK) {\n        appNotifyDisconnectCompleted(mydata);\n    } else {\n        appNotifyUnexpectedDisconnect(mydata);\n        appAttemptReconnect();\n    }\n}\n```\n\n### Sending commands and their callbacks\n\nIn an asynchronous context, commands are automatically pipelined due to the nature of an event loop.\nTherefore, unlike the synchronous API, there is only a single way to send commands.\nBecause commands are sent to Redis asynchronously, issuing a command requires a callback function\nthat is called when the reply is received. Reply callbacks should have the following prototype:\n```c\nvoid(redisAsyncContext *c, void *reply, void *privdata);\n```\nThe `privdata` argument can be used to curry arbitrary data to the callback from the point where\nthe command is initially queued for execution.\n\nThe functions that can be used to issue commands in an asynchronous context are:\n```c\nint redisAsyncCommand(\n  redisAsyncContext *ac, redisCallbackFn *fn, void *privdata,\n  const char *format, ...);\nint redisAsyncCommandArgv(\n  redisAsyncContext *ac, redisCallbackFn *fn, void *privdata,\n  int argc, const char **argv, const size_t *argvlen);\n```\nBoth functions work like their blocking counterparts. The return value is `REDIS_OK` when the command\nwas successfully added to the output buffer and `REDIS_ERR` otherwise. Example: when the connection\nis being disconnected per user-request, no new commands may be added to the output buffer and `REDIS_ERR` is\nreturned on calls to the `redisAsyncCommand` family.\n\nIf the reply for a command with a `NULL` callback is read, it is immediately freed. When the callback\nfor a command is non-`NULL`, the memory is freed immediately following the callback: the reply is only\nvalid for the duration of the callback.\n\nAll pending callbacks are called with a `NULL` reply when the context encountered an error.\n\nFor every command issued, with the exception of **SUBSCRIBE** and **PSUBSCRIBE**, the callback is\ncalled exactly once.  Even if the context object id disconnected or deleted, every pending callback\nwill be called with a `NULL` reply.\n\nFor **SUBSCRIBE** and **PSUBSCRIBE**, the callbacks may be called repeatedly until an `unsubscribe`\nmessage arrives.  This will be the last invocation of the callback. In case of error, the callbacks\nmay receive a final `NULL` reply instead.\n\n### Disconnecting\n\nAn asynchronous connection can be terminated using:\n```c\nvoid redisAsyncDisconnect(redisAsyncContext *ac);\n```\nWhen this function is called, the connection is **not** immediately terminated. Instead, new\ncommands are no longer accepted and the connection is only terminated when all pending commands\nhave been written to the socket, their respective replies have been read and their respective\ncallbacks have been executed. After this, the disconnection callback is executed with the\n`REDIS_OK` status and the context object is freed.\n\nThe connection can be forcefully disconnected using\n```c\nvoid redisAsyncFree(redisAsyncContext *ac);\n```\nIn this case, nothing more is written to the socket, all pending callbacks are called with a `NULL`\nreply and the disconnection callback is called with `REDIS_OK`, after which the context object\nis freed.\n\n\n### Hooking it up to event library *X*\n\nThere are a few hooks that need to be set on the context object after it is created.\nSee the `adapters/` directory for bindings to *libev* and *libevent*.\n\n## Reply parsing API\n\nHiredis comes with a reply parsing API that makes it easy for writing higher\nlevel language bindings.\n\nThe reply parsing API consists of the following functions:\n```c\nredisReader *redisReaderCreate(void);\nvoid redisReaderFree(redisReader *reader);\nint redisReaderFeed(redisReader *reader, const char *buf, size_t len);\nint redisReaderGetReply(redisReader *reader, void **reply);\n```\nThe same set of functions are used internally by hiredis when creating a\nnormal Redis context, the above API just exposes it to the user for a direct\nusage.\n\n### Usage\n\nThe function `redisReaderCreate` creates a `redisReader` structure that holds a\nbuffer with unparsed data and state for the protocol parser.\n\nIncoming data -- most likely from a socket -- can be placed in the internal\nbuffer of the `redisReader` using `redisReaderFeed`. This function will make a\ncopy of the buffer pointed to by `buf` for `len` bytes. This data is parsed\nwhen `redisReaderGetReply` is called. This function returns an integer status\nand a reply object (as described above) via `void **reply`. The returned status\ncan be either `REDIS_OK` or `REDIS_ERR`, where the latter means something went\nwrong (either a protocol error, or an out of memory error).\n\nThe parser limits the level of nesting for multi bulk payloads to 7. If the\nmulti bulk nesting level is higher than this, the parser returns an error.\n\n### Customizing replies\n\nThe function `redisReaderGetReply` creates `redisReply` and makes the function\nargument `reply` point to the created `redisReply` variable. For instance, if\nthe response of type `REDIS_REPLY_STATUS` then the `str` field of `redisReply`\nwill hold the status as a vanilla C string. However, the functions that are\nresponsible for creating instances of the `redisReply` can be customized by\nsetting the `fn` field on the `redisReader` struct. This should be done\nimmediately after creating the `redisReader`.\n\nFor example, [hiredis-rb](https://github.com/pietern/hiredis-rb/blob/master/ext/hiredis_ext/reader.c)\nuses customized reply object functions to create Ruby objects.\n\n### Reader max buffer\n\nBoth when using the Reader API directly or when using it indirectly via a\nnormal Redis context, the redisReader structure uses a buffer in order to\naccumulate data from the server.\nUsually this buffer is destroyed when it is empty and is larger than 16\nKiB in order to avoid wasting memory in unused buffers\n\nHowever when working with very big payloads destroying the buffer may slow\ndown performances considerably, so it is possible to modify the max size of\nan idle buffer changing the value of the `maxbuf` field of the reader structure\nto the desired value. The special value of 0 means that there is no maximum\nvalue for an idle buffer, so the buffer will never get freed.\n\nFor instance if you have a normal Redis context you can set the maximum idle\nbuffer to zero (unlimited) just with:\n```c\ncontext->reader->maxbuf = 0;\n```\nThis should be done only in order to maximize performances when working with\nlarge payloads. The context should be set back to `REDIS_READER_MAX_BUF` again\nas soon as possible in order to prevent allocation of useless memory.\n\n### Reader max array elements\n\nBy default the hiredis reply parser sets the maximum number of multi-bulk elements\nto 2^32 - 1 or 4,294,967,295 entries.  If you need to process multi-bulk replies\nwith more than this many elements you can set the value higher or to zero, meaning\nunlimited with:\n```c\ncontext->reader->maxelements = 0;\n```\n\n## SSL/TLS Support\n\n### Building\n\nSSL/TLS support is not built by default and requires an explicit flag:\n\n    make USE_SSL=1\n\nThis requires OpenSSL development package (e.g. including header files to be\navailable.\n\nWhen enabled, SSL/TLS support is built into extra `libhiredis_ssl.a` and\n`libhiredis_ssl.so` static/dynamic libraries. This leaves the original libraries\nunaffected so no additional dependencies are introduced.\n\n### Using it\n\nFirst, you'll need to make sure you include the SSL header file:\n\n```c\n#include <hiredis/hiredis.h>\n#include <hiredis/hiredis_ssl.h>\n```\n\nYou will also need to link against `libhiredis_ssl`, **in addition** to\n`libhiredis` and add `-lssl -lcrypto` to satisfy its dependencies.\n\nHiredis implements SSL/TLS on top of its normal `redisContext` or\n`redisAsyncContext`, so you will need to establish a connection first and then\ninitiate an SSL/TLS handshake.\n\n#### Hiredis OpenSSL Wrappers\n\nBefore Hiredis can negotiate an SSL/TLS connection, it is necessary to\ninitialize OpenSSL and create a context. You can do that in two ways:\n\n1. Work directly with the OpenSSL API to initialize the library's global context\n   and create `SSL_CTX *` and `SSL *` contexts. With an `SSL *` object you can\n   call `redisInitiateSSL()`.\n2. Work with a set of Hiredis-provided wrappers around OpenSSL, create a\n   `redisSSLContext` object to hold configuration and use\n   `redisInitiateSSLWithContext()` to initiate the SSL/TLS handshake.\n\n```c\n/* An Hiredis SSL context. It holds SSL configuration and can be reused across\n * many contexts.\n */\nredisSSLContext *ssl_context;\n\n/* An error variable to indicate what went wrong, if the context fails to\n * initialize.\n */\nredisSSLContextError ssl_error = REDIS_SSL_CTX_NONE;\n\n/* Initialize global OpenSSL state.\n *\n * You should call this only once when your app initializes, and only if\n * you don't explicitly or implicitly initialize OpenSSL it elsewhere.\n */\nredisInitOpenSSL();\n\n/* Create SSL context */\nssl_context = redisCreateSSLContext(\n    \"cacertbundle.crt\",     /* File name of trusted CA/ca bundle file, optional */\n    \"/path/to/certs\",       /* Path of trusted certificates, optional */\n    \"client_cert.pem\",      /* File name of client certificate file, optional */\n    \"client_key.pem\",       /* File name of client private key, optional */\n    \"redis.mydomain.com\",   /* Server name to request (SNI), optional */\n    &ssl_error);\n\nif(ssl_context == NULL || ssl_error != REDIS_SSL_CTX_NONE) {\n    /* Handle error and abort... */\n    /* e.g.\n    printf(\"SSL error: %s\\n\",\n        (ssl_error != REDIS_SSL_CTX_NONE) ?\n            redisSSLContextGetError(ssl_error) : \"Unknown error\");\n    // Abort\n    */\n}\n\n/* Create Redis context and establish connection */\nc = redisConnect(\"localhost\", 6443);\nif (c == NULL || c->err) {\n    /* Handle error and abort... */\n}\n\n/* Negotiate SSL/TLS */\nif (redisInitiateSSLWithContext(c, ssl_context) != REDIS_OK) {\n    /* Handle error, in c->err / c->errstr */\n}\n```\n\n## RESP3 PUSH replies\nRedis 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.\n\n### Default behavior\nHiredis 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`.\n\n### Custom PUSH handler prototypes\nThe callback prototypes differ between `redisContext` and `redisAsyncContext`.\n\n#### redisContext\n```c\nvoid my_push_handler(void *privdata, void *reply) {\n    /* Handle the reply */\n\n    /* Note: We need to free the reply in our custom handler for\n             blocking contexts.  This lets us keep the reply if\n             we want. */\n    freeReplyObject(reply);\n}\n```\n\n#### redisAsyncContext\n```c\nvoid my_async_push_handler(redisAsyncContext *ac, void *reply) {\n    /* Handle the reply */\n\n    /* Note:  Because async hiredis always frees replies, you should\n              not call freeReplyObject in an async push callback. */\n}\n```\n\n### Installing a custom handler\nThere are two ways to set your own PUSH handlers.\n\n1. Set `push_cb` or `async_push_cb` in the `redisOptions` struct and connect with `redisConnectWithOptions` or `redisAsyncConnectWithOptions`.\n    ```c\n    redisOptions = {0};\n    REDIS_OPTIONS_SET_TCP(&options, \"127.0.0.1\", 6379);\n    options->push_cb = my_push_handler;\n    redisContext *context = redisConnectWithOptions(&options);\n    ```\n2.  Call `redisSetPushCallback` or `redisAsyncSetPushCallback` on a connected context.\n    ```c\n    redisContext *context = redisConnect(\"127.0.0.1\", 6379);\n    redisSetPushCallback(context, my_push_handler);\n    ```\n\n    _Note `redisSetPushCallback` and `redisAsyncSetPushCallback` both return any currently configured handler,  making it easy to override and then return to the old value._\n\n### Specifying no handler\nIf 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.\n1.  Set the `REDIS_OPT_NO_PUSH_AUTOFREE` flag in `redisOptions` and leave the callback function pointer `NULL`.\n    ```c\n    redisOptions = {0};\n    REDIS_OPTIONS_SET_TCP(&options, \"127.0.0.1\", 6379);\n    options->options |= REDIS_OPT_NO_PUSH_AUTOFREE;\n    redisContext *context = redisConnectWithOptions(&options);\n    ```\n3.  Call `redisSetPushCallback` with `NULL` once connected.\n    ```c\n    redisContext *context = redisConnect(\"127.0.0.1\", 6379);\n    redisSetPushCallback(context, NULL);\n    ```\n\n    _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)._\n\n## Allocator injection\n\nHiredis 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).\n\n### Overriding\n\nOne can override the allocators like so:\n\n```c\nhiredisAllocFuncs myfuncs = {\n    .mallocFn = my_malloc,\n    .callocFn = my_calloc,\n    .reallocFn = my_realloc,\n    .strdupFn = my_strdup,\n    .freeFn = my_free,\n};\n\n// Override allocators (function returns current allocators if needed)\nhiredisAllocFuncs orig = hiredisSetAllocators(&myfuncs);\n```\n\nTo reset the allocators to their default libc function simply call:\n\n```c\nhiredisResetAllocators();\n```\n\n## AUTHORS\n\nSalvatore Sanfilippo (antirez at gmail),\\\nPieter Noordhuis (pcnoordhuis at gmail)\\\nMichael Grunder (michael dot grunder at gmail)\n\n_Hiredis is released under the BSD license._\n"
  },
  {
    "path": "adapters/ae.h",
    "content": "/*\n * Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com>\n *\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n *   * Redistributions of source code must retain the above copyright notice,\n *     this list of conditions and the following disclaimer.\n *   * Redistributions in binary form must reproduce the above copyright\n *     notice, this list of conditions and the following disclaimer in the\n *     documentation and/or other materials provided with the distribution.\n *   * Neither the name of Redis nor the names of its contributors may be used\n *     to endorse or promote products derived from this software without\n *     specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE\n * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n */\n\n#ifndef __HIREDIS_AE_H__\n#define __HIREDIS_AE_H__\n#include <sys/types.h>\n#include <ae.h>\n#include \"../hiredis.h\"\n#include \"../async.h\"\n\ntypedef struct redisAeEvents {\n    redisAsyncContext *context;\n    aeEventLoop *loop;\n    int fd;\n    int reading, writing;\n} redisAeEvents;\n\nstatic void redisAeReadEvent(aeEventLoop *el, int fd, void *privdata, int mask) {\n    ((void)el); ((void)fd); ((void)mask);\n\n    redisAeEvents *e = (redisAeEvents*)privdata;\n    redisAsyncHandleRead(e->context);\n}\n\nstatic void redisAeWriteEvent(aeEventLoop *el, int fd, void *privdata, int mask) {\n    ((void)el); ((void)fd); ((void)mask);\n\n    redisAeEvents *e = (redisAeEvents*)privdata;\n    redisAsyncHandleWrite(e->context);\n}\n\nstatic void redisAeAddRead(void *privdata) {\n    redisAeEvents *e = (redisAeEvents*)privdata;\n    aeEventLoop *loop = e->loop;\n    if (!e->reading) {\n        e->reading = 1;\n        aeCreateFileEvent(loop,e->fd,AE_READABLE,redisAeReadEvent,e);\n    }\n}\n\nstatic void redisAeDelRead(void *privdata) {\n    redisAeEvents *e = (redisAeEvents*)privdata;\n    aeEventLoop *loop = e->loop;\n    if (e->reading) {\n        e->reading = 0;\n        aeDeleteFileEvent(loop,e->fd,AE_READABLE);\n    }\n}\n\nstatic void redisAeAddWrite(void *privdata) {\n    redisAeEvents *e = (redisAeEvents*)privdata;\n    aeEventLoop *loop = e->loop;\n    if (!e->writing) {\n        e->writing = 1;\n        aeCreateFileEvent(loop,e->fd,AE_WRITABLE,redisAeWriteEvent,e);\n    }\n}\n\nstatic void redisAeDelWrite(void *privdata) {\n    redisAeEvents *e = (redisAeEvents*)privdata;\n    aeEventLoop *loop = e->loop;\n    if (e->writing) {\n        e->writing = 0;\n        aeDeleteFileEvent(loop,e->fd,AE_WRITABLE);\n    }\n}\n\nstatic void redisAeCleanup(void *privdata) {\n    redisAeEvents *e = (redisAeEvents*)privdata;\n    redisAeDelRead(privdata);\n    redisAeDelWrite(privdata);\n    hi_free(e);\n}\n\nstatic int redisAeAttach(aeEventLoop *loop, redisAsyncContext *ac) {\n    redisContext *c = &(ac->c);\n    redisAeEvents *e;\n\n    /* Nothing should be attached when something is already attached */\n    if (ac->ev.data != NULL)\n        return REDIS_ERR;\n\n    /* Create container for context and r/w events */\n    e = (redisAeEvents*)hi_malloc(sizeof(*e));\n    if (e == NULL)\n        return REDIS_ERR;\n\n    e->context = ac;\n    e->loop = loop;\n    e->fd = c->fd;\n    e->reading = e->writing = 0;\n\n    /* Register functions to start/stop listening for events */\n    ac->ev.addRead = redisAeAddRead;\n    ac->ev.delRead = redisAeDelRead;\n    ac->ev.addWrite = redisAeAddWrite;\n    ac->ev.delWrite = redisAeDelWrite;\n    ac->ev.cleanup = redisAeCleanup;\n    ac->ev.data = e;\n\n    return REDIS_OK;\n}\n#endif\n"
  },
  {
    "path": "adapters/glib.h",
    "content": "#ifndef __HIREDIS_GLIB_H__\n#define __HIREDIS_GLIB_H__\n\n#include <glib.h>\n\n#include \"../hiredis.h\"\n#include \"../async.h\"\n\ntypedef struct\n{\n    GSource source;\n    redisAsyncContext *ac;\n    GPollFD poll_fd;\n} RedisSource;\n\nstatic void\nredis_source_add_read (gpointer data)\n{\n    RedisSource *source = (RedisSource *)data;\n    g_return_if_fail(source);\n    source->poll_fd.events |= G_IO_IN;\n    g_main_context_wakeup(g_source_get_context((GSource *)data));\n}\n\nstatic void\nredis_source_del_read (gpointer data)\n{\n    RedisSource *source = (RedisSource *)data;\n    g_return_if_fail(source);\n    source->poll_fd.events &= ~G_IO_IN;\n    g_main_context_wakeup(g_source_get_context((GSource *)data));\n}\n\nstatic void\nredis_source_add_write (gpointer data)\n{\n    RedisSource *source = (RedisSource *)data;\n    g_return_if_fail(source);\n    source->poll_fd.events |= G_IO_OUT;\n    g_main_context_wakeup(g_source_get_context((GSource *)data));\n}\n\nstatic void\nredis_source_del_write (gpointer data)\n{\n    RedisSource *source = (RedisSource *)data;\n    g_return_if_fail(source);\n    source->poll_fd.events &= ~G_IO_OUT;\n    g_main_context_wakeup(g_source_get_context((GSource *)data));\n}\n\nstatic void\nredis_source_cleanup (gpointer data)\n{\n    RedisSource *source = (RedisSource *)data;\n\n    g_return_if_fail(source);\n\n    redis_source_del_read(source);\n    redis_source_del_write(source);\n    /*\n     * It is not our responsibility to remove ourself from the\n     * current main loop. However, we will remove the GPollFD.\n     */\n    if (source->poll_fd.fd >= 0) {\n        g_source_remove_poll((GSource *)data, &source->poll_fd);\n        source->poll_fd.fd = -1;\n    }\n}\n\nstatic gboolean\nredis_source_prepare (GSource *source,\n                      gint    *timeout_)\n{\n    RedisSource *redis = (RedisSource *)source;\n    *timeout_ = -1;\n    return !!(redis->poll_fd.events & redis->poll_fd.revents);\n}\n\nstatic gboolean\nredis_source_check (GSource *source)\n{\n    RedisSource *redis = (RedisSource *)source;\n    return !!(redis->poll_fd.events & redis->poll_fd.revents);\n}\n\nstatic gboolean\nredis_source_dispatch (GSource      *source,\n                       GSourceFunc   callback,\n                       gpointer      user_data)\n{\n    RedisSource *redis = (RedisSource *)source;\n\n    if ((redis->poll_fd.revents & G_IO_OUT)) {\n        redisAsyncHandleWrite(redis->ac);\n        redis->poll_fd.revents &= ~G_IO_OUT;\n    }\n\n    if ((redis->poll_fd.revents & G_IO_IN)) {\n        redisAsyncHandleRead(redis->ac);\n        redis->poll_fd.revents &= ~G_IO_IN;\n    }\n\n    if (callback) {\n        return callback(user_data);\n    }\n\n    return TRUE;\n}\n\nstatic void\nredis_source_finalize (GSource *source)\n{\n    RedisSource *redis = (RedisSource *)source;\n\n    if (redis->poll_fd.fd >= 0) {\n        g_source_remove_poll(source, &redis->poll_fd);\n        redis->poll_fd.fd = -1;\n    }\n}\n\nstatic GSource *\nredis_source_new (redisAsyncContext *ac)\n{\n    static GSourceFuncs source_funcs = {\n        .prepare  = redis_source_prepare,\n        .check     = redis_source_check,\n        .dispatch = redis_source_dispatch,\n        .finalize = redis_source_finalize,\n    };\n    redisContext *c = &ac->c;\n    RedisSource *source;\n\n    g_return_val_if_fail(ac != NULL, NULL);\n\n    source = (RedisSource *)g_source_new(&source_funcs, sizeof *source);\n    if (source == NULL)\n        return NULL;\n\n    source->ac = ac;\n    source->poll_fd.fd = c->fd;\n    source->poll_fd.events = 0;\n    source->poll_fd.revents = 0;\n    g_source_add_poll((GSource *)source, &source->poll_fd);\n\n    ac->ev.addRead = redis_source_add_read;\n    ac->ev.delRead = redis_source_del_read;\n    ac->ev.addWrite = redis_source_add_write;\n    ac->ev.delWrite = redis_source_del_write;\n    ac->ev.cleanup = redis_source_cleanup;\n    ac->ev.data = source;\n\n    return (GSource *)source;\n}\n\n#endif /* __HIREDIS_GLIB_H__ */\n"
  },
  {
    "path": "adapters/ivykis.h",
    "content": "#ifndef __HIREDIS_IVYKIS_H__\n#define __HIREDIS_IVYKIS_H__\n#include <iv.h>\n#include \"../hiredis.h\"\n#include \"../async.h\"\n\ntypedef struct redisIvykisEvents {\n    redisAsyncContext *context;\n    struct iv_fd fd;\n} redisIvykisEvents;\n\nstatic void redisIvykisReadEvent(void *arg) {\n    redisAsyncContext *context = (redisAsyncContext *)arg;\n    redisAsyncHandleRead(context);\n}\n\nstatic void redisIvykisWriteEvent(void *arg) {\n    redisAsyncContext *context = (redisAsyncContext *)arg;\n    redisAsyncHandleWrite(context);\n}\n\nstatic void redisIvykisAddRead(void *privdata) {\n    redisIvykisEvents *e = (redisIvykisEvents*)privdata;\n    iv_fd_set_handler_in(&e->fd, redisIvykisReadEvent);\n}\n\nstatic void redisIvykisDelRead(void *privdata) {\n    redisIvykisEvents *e = (redisIvykisEvents*)privdata;\n    iv_fd_set_handler_in(&e->fd, NULL);\n}\n\nstatic void redisIvykisAddWrite(void *privdata) {\n    redisIvykisEvents *e = (redisIvykisEvents*)privdata;\n    iv_fd_set_handler_out(&e->fd, redisIvykisWriteEvent);\n}\n\nstatic void redisIvykisDelWrite(void *privdata) {\n    redisIvykisEvents *e = (redisIvykisEvents*)privdata;\n    iv_fd_set_handler_out(&e->fd, NULL);\n}\n\nstatic void redisIvykisCleanup(void *privdata) {\n    redisIvykisEvents *e = (redisIvykisEvents*)privdata;\n\n    iv_fd_unregister(&e->fd);\n    hi_free(e);\n}\n\nstatic int redisIvykisAttach(redisAsyncContext *ac) {\n    redisContext *c = &(ac->c);\n    redisIvykisEvents *e;\n\n    /* Nothing should be attached when something is already attached */\n    if (ac->ev.data != NULL)\n        return REDIS_ERR;\n\n    /* Create container for context and r/w events */\n    e = (redisIvykisEvents*)hi_malloc(sizeof(*e));\n    if (e == NULL)\n        return REDIS_ERR;\n\n    e->context = ac;\n\n    /* Register functions to start/stop listening for events */\n    ac->ev.addRead = redisIvykisAddRead;\n    ac->ev.delRead = redisIvykisDelRead;\n    ac->ev.addWrite = redisIvykisAddWrite;\n    ac->ev.delWrite = redisIvykisDelWrite;\n    ac->ev.cleanup = redisIvykisCleanup;\n    ac->ev.data = e;\n\n    /* Initialize and install read/write events */\n    IV_FD_INIT(&e->fd);\n    e->fd.fd = c->fd;\n    e->fd.handler_in = redisIvykisReadEvent;\n    e->fd.handler_out = redisIvykisWriteEvent;\n    e->fd.handler_err = NULL;\n    e->fd.cookie = e->context;\n\n    iv_fd_register(&e->fd);\n\n    return REDIS_OK;\n}\n#endif\n"
  },
  {
    "path": "adapters/libev.h",
    "content": "/*\n * Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com>\n *\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n *   * Redistributions of source code must retain the above copyright notice,\n *     this list of conditions and the following disclaimer.\n *   * Redistributions in binary form must reproduce the above copyright\n *     notice, this list of conditions and the following disclaimer in the\n *     documentation and/or other materials provided with the distribution.\n *   * Neither the name of Redis nor the names of its contributors may be used\n *     to endorse or promote products derived from this software without\n *     specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE\n * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n */\n\n#ifndef __HIREDIS_LIBEV_H__\n#define __HIREDIS_LIBEV_H__\n#include <stdlib.h>\n#include <sys/types.h>\n#include <ev.h>\n#include \"../hiredis.h\"\n#include \"../async.h\"\n\ntypedef struct redisLibevEvents {\n    redisAsyncContext *context;\n    struct ev_loop *loop;\n    int reading, writing;\n    ev_io rev, wev;\n    ev_timer timer;\n} redisLibevEvents;\n\nstatic void redisLibevReadEvent(EV_P_ ev_io *watcher, int revents) {\n#if EV_MULTIPLICITY\n    ((void)EV_A);\n#endif\n    ((void)revents);\n\n    redisLibevEvents *e = (redisLibevEvents*)watcher->data;\n    redisAsyncHandleRead(e->context);\n}\n\nstatic void redisLibevWriteEvent(EV_P_ ev_io *watcher, int revents) {\n#if EV_MULTIPLICITY\n    ((void)EV_A);\n#endif\n    ((void)revents);\n\n    redisLibevEvents *e = (redisLibevEvents*)watcher->data;\n    redisAsyncHandleWrite(e->context);\n}\n\nstatic void redisLibevAddRead(void *privdata) {\n    redisLibevEvents *e = (redisLibevEvents*)privdata;\n#if EV_MULTIPLICITY\n    struct ev_loop *loop = e->loop;\n#endif\n    if (!e->reading) {\n        e->reading = 1;\n        ev_io_start(EV_A_ &e->rev);\n    }\n}\n\nstatic void redisLibevDelRead(void *privdata) {\n    redisLibevEvents *e = (redisLibevEvents*)privdata;\n#if EV_MULTIPLICITY\n    struct ev_loop *loop = e->loop;\n#endif\n    if (e->reading) {\n        e->reading = 0;\n        ev_io_stop(EV_A_ &e->rev);\n    }\n}\n\nstatic void redisLibevAddWrite(void *privdata) {\n    redisLibevEvents *e = (redisLibevEvents*)privdata;\n#if EV_MULTIPLICITY\n    struct ev_loop *loop = e->loop;\n#endif\n    if (!e->writing) {\n        e->writing = 1;\n        ev_io_start(EV_A_ &e->wev);\n    }\n}\n\nstatic void redisLibevDelWrite(void *privdata) {\n    redisLibevEvents *e = (redisLibevEvents*)privdata;\n#if EV_MULTIPLICITY\n    struct ev_loop *loop = e->loop;\n#endif\n    if (e->writing) {\n        e->writing = 0;\n        ev_io_stop(EV_A_ &e->wev);\n    }\n}\n\nstatic void redisLibevStopTimer(void *privdata) {\n    redisLibevEvents *e = (redisLibevEvents*)privdata;\n#if EV_MULTIPLICITY\n    struct ev_loop *loop = e->loop;\n#endif\n    ev_timer_stop(EV_A_ &e->timer);\n}\n\nstatic void redisLibevCleanup(void *privdata) {\n    redisLibevEvents *e = (redisLibevEvents*)privdata;\n    redisLibevDelRead(privdata);\n    redisLibevDelWrite(privdata);\n    redisLibevStopTimer(privdata);\n    hi_free(e);\n}\n\nstatic void redisLibevTimeout(EV_P_ ev_timer *timer, int revents) {\n#if EV_MULTIPLICITY\n    ((void)EV_A);\n#endif\n    ((void)revents);\n    redisLibevEvents *e = (redisLibevEvents*)timer->data;\n    redisAsyncHandleTimeout(e->context);\n}\n\nstatic void redisLibevSetTimeout(void *privdata, struct timeval tv) {\n    redisLibevEvents *e = (redisLibevEvents*)privdata;\n#if EV_MULTIPLICITY\n    struct ev_loop *loop = e->loop;\n#endif\n\n    if (!ev_is_active(&e->timer)) {\n        ev_init(&e->timer, redisLibevTimeout);\n        e->timer.data = e;\n    }\n\n    e->timer.repeat = tv.tv_sec + tv.tv_usec / 1000000.00;\n    ev_timer_again(EV_A_ &e->timer);\n}\n\nstatic int redisLibevAttach(EV_P_ redisAsyncContext *ac) {\n    redisContext *c = &(ac->c);\n    redisLibevEvents *e;\n\n    /* Nothing should be attached when something is already attached */\n    if (ac->ev.data != NULL)\n        return REDIS_ERR;\n\n    /* Create container for context and r/w events */\n    e = (redisLibevEvents*)hi_calloc(1, sizeof(*e));\n    if (e == NULL)\n        return REDIS_ERR;\n\n    e->context = ac;\n#if EV_MULTIPLICITY\n    e->loop = EV_A;\n#else\n    e->loop = NULL;\n#endif\n    e->rev.data = e;\n    e->wev.data = e;\n\n    /* Register functions to start/stop listening for events */\n    ac->ev.addRead = redisLibevAddRead;\n    ac->ev.delRead = redisLibevDelRead;\n    ac->ev.addWrite = redisLibevAddWrite;\n    ac->ev.delWrite = redisLibevDelWrite;\n    ac->ev.cleanup = redisLibevCleanup;\n    ac->ev.scheduleTimer = redisLibevSetTimeout;\n    ac->ev.data = e;\n\n    /* Initialize read/write events */\n    ev_io_init(&e->rev,redisLibevReadEvent,c->fd,EV_READ);\n    ev_io_init(&e->wev,redisLibevWriteEvent,c->fd,EV_WRITE);\n    return REDIS_OK;\n}\n\n#endif\n"
  },
  {
    "path": "adapters/libevent.h",
    "content": "/*\n * Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com>\n *\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n *   * Redistributions of source code must retain the above copyright notice,\n *     this list of conditions and the following disclaimer.\n *   * Redistributions in binary form must reproduce the above copyright\n *     notice, this list of conditions and the following disclaimer in the\n *     documentation and/or other materials provided with the distribution.\n *   * Neither the name of Redis nor the names of its contributors may be used\n *     to endorse or promote products derived from this software without\n *     specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE\n * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n */\n\n#ifndef __HIREDIS_LIBEVENT_H__\n#define __HIREDIS_LIBEVENT_H__\n#include <event2/event.h>\n#include \"../hiredis.h\"\n#include \"../async.h\"\n\n#define REDIS_LIBEVENT_DELETED 0x01\n#define REDIS_LIBEVENT_ENTERED 0x02\n\ntypedef struct redisLibeventEvents {\n    redisAsyncContext *context;\n    struct event *ev;\n    struct event_base *base;\n    struct timeval tv;\n    short flags;\n    short state;\n} redisLibeventEvents;\n\nstatic void redisLibeventDestroy(redisLibeventEvents *e) {\n    hi_free(e);\n}\n\nstatic void redisLibeventHandler(evutil_socket_t fd, short event, void *arg) {\n    ((void)fd);\n    redisLibeventEvents *e = (redisLibeventEvents*)arg;\n    e->state |= REDIS_LIBEVENT_ENTERED;\n\n    #define CHECK_DELETED() if (e->state & REDIS_LIBEVENT_DELETED) {\\\n        redisLibeventDestroy(e);\\\n        return; \\\n    }\n\n    if ((event & EV_TIMEOUT) && (e->state & REDIS_LIBEVENT_DELETED) == 0) {\n        redisAsyncHandleTimeout(e->context);\n        CHECK_DELETED();\n    }\n\n    if ((event & EV_READ) && e->context && (e->state & REDIS_LIBEVENT_DELETED) == 0) {\n        redisAsyncHandleRead(e->context);\n        CHECK_DELETED();\n    }\n\n    if ((event & EV_WRITE) && e->context && (e->state & REDIS_LIBEVENT_DELETED) == 0) {\n        redisAsyncHandleWrite(e->context);\n        CHECK_DELETED();\n    }\n\n    e->state &= ~REDIS_LIBEVENT_ENTERED;\n    #undef CHECK_DELETED\n}\n\nstatic void redisLibeventUpdate(void *privdata, short flag, int isRemove) {\n    redisLibeventEvents *e = (redisLibeventEvents *)privdata;\n    const struct timeval *tv = e->tv.tv_sec || e->tv.tv_usec ? &e->tv : NULL;\n\n    if (isRemove) {\n        if ((e->flags & flag) == 0) {\n            return;\n        } else {\n            e->flags &= ~flag;\n        }\n    } else {\n        if (e->flags & flag) {\n            return;\n        } else {\n            e->flags |= flag;\n        }\n    }\n\n    event_del(e->ev);\n    event_assign(e->ev, e->base, e->context->c.fd, e->flags | EV_PERSIST,\n                 redisLibeventHandler, privdata);\n    event_add(e->ev, tv);\n}\n\nstatic void redisLibeventAddRead(void *privdata) {\n    redisLibeventUpdate(privdata, EV_READ, 0);\n}\n\nstatic void redisLibeventDelRead(void *privdata) {\n    redisLibeventUpdate(privdata, EV_READ, 1);\n}\n\nstatic void redisLibeventAddWrite(void *privdata) {\n    redisLibeventUpdate(privdata, EV_WRITE, 0);\n}\n\nstatic void redisLibeventDelWrite(void *privdata) {\n    redisLibeventUpdate(privdata, EV_WRITE, 1);\n}\n\nstatic void redisLibeventCleanup(void *privdata) {\n    redisLibeventEvents *e = (redisLibeventEvents*)privdata;\n    if (!e) {\n        return;\n    }\n    event_del(e->ev);\n    event_free(e->ev);\n    e->ev = NULL;\n\n    if (e->state & REDIS_LIBEVENT_ENTERED) {\n        e->state |= REDIS_LIBEVENT_DELETED;\n    } else {\n        redisLibeventDestroy(e);\n    }\n}\n\nstatic void redisLibeventSetTimeout(void *privdata, struct timeval tv) {\n    redisLibeventEvents *e = (redisLibeventEvents *)privdata;\n    short flags = e->flags;\n    e->flags = 0;\n    e->tv = tv;\n    redisLibeventUpdate(e, flags, 0);\n}\n\nstatic int redisLibeventAttach(redisAsyncContext *ac, struct event_base *base) {\n    redisContext *c = &(ac->c);\n    redisLibeventEvents *e;\n\n    /* Nothing should be attached when something is already attached */\n    if (ac->ev.data != NULL)\n        return REDIS_ERR;\n\n    /* Create container for context and r/w events */\n    e = (redisLibeventEvents*)hi_calloc(1, sizeof(*e));\n    if (e == NULL)\n        return REDIS_ERR;\n\n    e->context = ac;\n\n    /* Register functions to start/stop listening for events */\n    ac->ev.addRead = redisLibeventAddRead;\n    ac->ev.delRead = redisLibeventDelRead;\n    ac->ev.addWrite = redisLibeventAddWrite;\n    ac->ev.delWrite = redisLibeventDelWrite;\n    ac->ev.cleanup = redisLibeventCleanup;\n    ac->ev.scheduleTimer = redisLibeventSetTimeout;\n    ac->ev.data = e;\n\n    /* Initialize and install read/write events */\n    e->ev = event_new(base, c->fd, EV_READ | EV_WRITE, redisLibeventHandler, e);\n    e->base = base;\n    return REDIS_OK;\n}\n#endif\n"
  },
  {
    "path": "adapters/libhv.h",
    "content": "#ifndef __HIREDIS_LIBHV_H__\n#define __HIREDIS_LIBHV_H__\n\n#include <hv/hloop.h>\n#include \"../hiredis.h\"\n#include \"../async.h\"\n\ntypedef struct redisLibhvEvents {\n    hio_t *io;\n    htimer_t *timer;\n} redisLibhvEvents;\n\nstatic void redisLibhvHandleEvents(hio_t* io) {\n    redisAsyncContext* context = (redisAsyncContext*)hevent_userdata(io);\n    int events = hio_events(io);\n    int revents = hio_revents(io);\n    if (context && (events & HV_READ) && (revents & HV_READ)) {\n        redisAsyncHandleRead(context);\n    }\n    if (context && (events & HV_WRITE) && (revents & HV_WRITE)) {\n        redisAsyncHandleWrite(context);\n    }\n}\n\nstatic void redisLibhvAddRead(void *privdata) {\n    redisLibhvEvents* events = (redisLibhvEvents*)privdata;\n    hio_add(events->io, redisLibhvHandleEvents, HV_READ);\n}\n\nstatic void redisLibhvDelRead(void *privdata) {\n    redisLibhvEvents* events = (redisLibhvEvents*)privdata;\n    hio_del(events->io, HV_READ);\n}\n\nstatic void redisLibhvAddWrite(void *privdata) {\n    redisLibhvEvents* events = (redisLibhvEvents*)privdata;\n    hio_add(events->io, redisLibhvHandleEvents, HV_WRITE);\n}\n\nstatic void redisLibhvDelWrite(void *privdata) {\n    redisLibhvEvents* events = (redisLibhvEvents*)privdata;\n    hio_del(events->io, HV_WRITE);\n}\n\nstatic void redisLibhvCleanup(void *privdata) {\n    redisLibhvEvents* events = (redisLibhvEvents*)privdata;\n\n    if (events->timer)\n        htimer_del(events->timer);\n\n    hio_close(events->io);\n    hevent_set_userdata(events->io, NULL);\n\n    hi_free(events);\n}\n\nstatic void redisLibhvTimeout(htimer_t* timer) {\n    hio_t* io = (hio_t*)hevent_userdata(timer);\n    redisAsyncHandleTimeout((redisAsyncContext*)hevent_userdata(io));\n}\n\nstatic void redisLibhvSetTimeout(void *privdata, struct timeval tv) {\n    redisLibhvEvents* events;\n    uint32_t millis;\n    hloop_t* loop;\n\n    events = (redisLibhvEvents*)privdata;\n    millis = tv.tv_sec * 1000 + tv.tv_usec / 1000;\n\n    if (millis == 0) {\n        /* Libhv disallows zero'd timers so treat this as a delete or NO OP */\n        if (events->timer) {\n            htimer_del(events->timer);\n            events->timer = NULL;\n        }\n    } else if (events->timer == NULL) {\n        /* Add new timer */\n        loop = hevent_loop(events->io);\n        events->timer = htimer_add(loop, redisLibhvTimeout, millis, 1);\n        hevent_set_userdata(events->timer, events->io);\n    } else {\n        /* Update existing timer */\n        htimer_reset(events->timer, millis);\n    }\n}\n\nstatic int redisLibhvAttach(redisAsyncContext* ac, hloop_t* loop) {\n    redisContext *c = &(ac->c);\n    redisLibhvEvents *events;\n    hio_t* io = NULL;\n\n    if (ac->ev.data != NULL) {\n        return REDIS_ERR;\n    }\n\n    /* Create container struct to keep track of our io and any timer */\n    events = (redisLibhvEvents*)hi_malloc(sizeof(*events));\n    if (events == NULL) {\n        return REDIS_ERR;\n    }\n\n    io = hio_get(loop, c->fd);\n    if (io == NULL) {\n        hi_free(events);\n        return REDIS_ERR;\n    }\n\n    hevent_set_userdata(io, ac);\n\n    events->io = io;\n    events->timer = NULL;\n\n    ac->ev.addRead  = redisLibhvAddRead;\n    ac->ev.delRead  = redisLibhvDelRead;\n    ac->ev.addWrite = redisLibhvAddWrite;\n    ac->ev.delWrite = redisLibhvDelWrite;\n    ac->ev.cleanup  = redisLibhvCleanup;\n    ac->ev.scheduleTimer = redisLibhvSetTimeout;\n    ac->ev.data = events;\n\n    return REDIS_OK;\n}\n#endif\n"
  },
  {
    "path": "adapters/libsdevent.h",
    "content": "#ifndef HIREDIS_LIBSDEVENT_H\n#define HIREDIS_LIBSDEVENT_H\n#include <systemd/sd-event.h>\n#include \"../hiredis.h\"\n#include \"../async.h\"\n\n#define REDIS_LIBSDEVENT_DELETED 0x01\n#define REDIS_LIBSDEVENT_ENTERED 0x02\n\ntypedef struct redisLibsdeventEvents {\n    redisAsyncContext *context;\n    struct sd_event *event;\n    struct sd_event_source *fdSource;\n    struct sd_event_source *timerSource;\n    int fd;\n    short flags;\n    short state;\n} redisLibsdeventEvents;\n\nstatic void redisLibsdeventDestroy(redisLibsdeventEvents *e) {\n    if (e->fdSource) {\n        e->fdSource = sd_event_source_disable_unref(e->fdSource);\n    }\n    if (e->timerSource) {\n        e->timerSource = sd_event_source_disable_unref(e->timerSource);\n    }\n    sd_event_unref(e->event);\n    hi_free(e);\n}\n\nstatic int redisLibsdeventTimeoutHandler(sd_event_source *s, uint64_t usec, void *userdata) {\n    ((void)s);\n    ((void)usec);\n    redisLibsdeventEvents *e = (redisLibsdeventEvents*)userdata;\n    redisAsyncHandleTimeout(e->context);\n    return 0;\n}\n\nstatic int redisLibsdeventHandler(sd_event_source *s, int fd, uint32_t event, void *userdata) {\n    ((void)s);\n    ((void)fd);\n    redisLibsdeventEvents *e = (redisLibsdeventEvents*)userdata;\n    e->state |= REDIS_LIBSDEVENT_ENTERED;\n\n#define CHECK_DELETED() if (e->state & REDIS_LIBSDEVENT_DELETED) {\\\n        redisLibsdeventDestroy(e);\\\n        return 0; \\\n    }\n\n    if ((event & EPOLLIN) && e->context && (e->state & REDIS_LIBSDEVENT_DELETED) == 0) {\n        redisAsyncHandleRead(e->context);\n        CHECK_DELETED();\n    }\n\n    if ((event & EPOLLOUT) && e->context && (e->state & REDIS_LIBSDEVENT_DELETED) == 0) {\n        redisAsyncHandleWrite(e->context);\n        CHECK_DELETED();\n    }\n\n    e->state &= ~REDIS_LIBSDEVENT_ENTERED;\n#undef CHECK_DELETED\n\n    return 0;\n}\n\nstatic void redisLibsdeventAddRead(void *userdata) {\n    redisLibsdeventEvents *e = (redisLibsdeventEvents*)userdata;\n\n    if (e->flags & EPOLLIN) {\n        return;\n    }\n\n    e->flags |= EPOLLIN;\n\n    if (e->flags & EPOLLOUT) {\n        sd_event_source_set_io_events(e->fdSource, e->flags);\n    } else {\n        sd_event_add_io(e->event, &e->fdSource, e->fd, e->flags, redisLibsdeventHandler, e);\n    }\n}\n\nstatic void redisLibsdeventDelRead(void *userdata) {\n    redisLibsdeventEvents *e = (redisLibsdeventEvents*)userdata;\n\n    e->flags &= ~EPOLLIN;\n\n    if (e->flags) {\n        sd_event_source_set_io_events(e->fdSource, e->flags);\n    } else {\n        e->fdSource = sd_event_source_disable_unref(e->fdSource);\n    }\n}\n\nstatic void redisLibsdeventAddWrite(void *userdata) {\n    redisLibsdeventEvents *e = (redisLibsdeventEvents*)userdata;\n\n    if (e->flags & EPOLLOUT) {\n        return;\n    }\n\n    e->flags |= EPOLLOUT;\n\n    if (e->flags & EPOLLIN) {\n        sd_event_source_set_io_events(e->fdSource, e->flags);\n    } else {\n        sd_event_add_io(e->event, &e->fdSource, e->fd, e->flags, redisLibsdeventHandler, e);\n    }\n}\n\nstatic void redisLibsdeventDelWrite(void *userdata) {\n    redisLibsdeventEvents *e = (redisLibsdeventEvents*)userdata;\n\n    e->flags &= ~EPOLLOUT;\n\n    if (e->flags) {\n        sd_event_source_set_io_events(e->fdSource, e->flags);\n    } else {\n        e->fdSource = sd_event_source_disable_unref(e->fdSource);\n    }\n}\n\nstatic void redisLibsdeventCleanup(void *userdata) {\n    redisLibsdeventEvents *e = (redisLibsdeventEvents*)userdata;\n\n    if (!e) {\n        return;\n    }\n\n    if (e->state & REDIS_LIBSDEVENT_ENTERED) {\n        e->state |= REDIS_LIBSDEVENT_DELETED;\n    } else {\n        redisLibsdeventDestroy(e);\n    }\n}\n\nstatic void redisLibsdeventSetTimeout(void *userdata, struct timeval tv) {\n    redisLibsdeventEvents *e = (redisLibsdeventEvents *)userdata;\n\n    uint64_t usec = tv.tv_sec * 1000000 + tv.tv_usec;\n    if (!e->timerSource) {\n        sd_event_add_time_relative(e->event, &e->timerSource, CLOCK_MONOTONIC, usec, 1, redisLibsdeventTimeoutHandler, e);\n    } else {\n        sd_event_source_set_time_relative(e->timerSource, usec);\n    }\n}\n\nstatic int redisLibsdeventAttach(redisAsyncContext *ac, struct sd_event *event) {\n    redisContext *c = &(ac->c);\n    redisLibsdeventEvents *e;\n\n    /* Nothing should be attached when something is already attached */\n    if (ac->ev.data != NULL)\n        return REDIS_ERR;\n\n    /* Create container for context and r/w events */\n    e = (redisLibsdeventEvents*)hi_calloc(1, sizeof(*e));\n    if (e == NULL)\n        return REDIS_ERR;\n\n    /* Initialize and increase event refcount */\n    e->context = ac;\n    e->event = event;\n    e->fd = c->fd;\n    sd_event_ref(event);\n\n    /* Register functions to start/stop listening for events */\n    ac->ev.addRead = redisLibsdeventAddRead;\n    ac->ev.delRead = redisLibsdeventDelRead;\n    ac->ev.addWrite = redisLibsdeventAddWrite;\n    ac->ev.delWrite = redisLibsdeventDelWrite;\n    ac->ev.cleanup = redisLibsdeventCleanup;\n    ac->ev.scheduleTimer = redisLibsdeventSetTimeout;\n    ac->ev.data = e;\n\n    return REDIS_OK;\n}\n#endif\n"
  },
  {
    "path": "adapters/libuv.h",
    "content": "#ifndef __HIREDIS_LIBUV_H__\n#define __HIREDIS_LIBUV_H__\n#include <stdlib.h>\n#include <uv.h>\n#include \"../hiredis.h\"\n#include \"../async.h\"\n#include <string.h>\n\ntypedef struct redisLibuvEvents {\n    redisAsyncContext* context;\n    uv_poll_t          handle;\n    uv_timer_t         timer;\n    int                events;\n} redisLibuvEvents;\n\n\nstatic void redisLibuvPoll(uv_poll_t* handle, int status, int events) {\n    redisLibuvEvents* p = (redisLibuvEvents*)handle->data;\n    int ev = (status ? p->events : events);\n\n    if (p->context != NULL && (ev & UV_READABLE)) {\n        redisAsyncHandleRead(p->context);\n    }\n    if (p->context != NULL && (ev & UV_WRITABLE)) {\n        redisAsyncHandleWrite(p->context);\n    }\n}\n\n\nstatic void redisLibuvAddRead(void *privdata) {\n    redisLibuvEvents* p = (redisLibuvEvents*)privdata;\n\n    if (p->events & UV_READABLE) {\n        return;\n    }\n\n    p->events |= UV_READABLE;\n\n    uv_poll_start(&p->handle, p->events, redisLibuvPoll);\n}\n\n\nstatic void redisLibuvDelRead(void *privdata) {\n    redisLibuvEvents* p = (redisLibuvEvents*)privdata;\n\n    p->events &= ~UV_READABLE;\n\n    if (p->events) {\n        uv_poll_start(&p->handle, p->events, redisLibuvPoll);\n    } else {\n        uv_poll_stop(&p->handle);\n    }\n}\n\n\nstatic void redisLibuvAddWrite(void *privdata) {\n    redisLibuvEvents* p = (redisLibuvEvents*)privdata;\n\n    if (p->events & UV_WRITABLE) {\n        return;\n    }\n\n    p->events |= UV_WRITABLE;\n\n    uv_poll_start(&p->handle, p->events, redisLibuvPoll);\n}\n\n\nstatic void redisLibuvDelWrite(void *privdata) {\n    redisLibuvEvents* p = (redisLibuvEvents*)privdata;\n\n    p->events &= ~UV_WRITABLE;\n\n    if (p->events) {\n        uv_poll_start(&p->handle, p->events, redisLibuvPoll);\n    } else {\n        uv_poll_stop(&p->handle);\n    }\n}\n\nstatic void on_timer_close(uv_handle_t *handle) {\n    redisLibuvEvents* p = (redisLibuvEvents*)handle->data;\n    p->timer.data = NULL;\n    if (!p->handle.data) {\n        // both timer and handle are closed\n        hi_free(p);\n    }\n    // else, wait for `on_handle_close`\n}\n\nstatic void on_handle_close(uv_handle_t *handle) {\n    redisLibuvEvents* p = (redisLibuvEvents*)handle->data;\n    p->handle.data = NULL;\n    if (!p->timer.data) {\n        // timer never started, or timer already destroyed\n        hi_free(p);\n    }\n    // else, wait for `on_timer_close`\n}\n\n// libuv removed `status` parameter since v0.11.23\n// see: https://github.com/libuv/libuv/blob/v0.11.23/include/uv.h\n#if (UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR < 11) || \\\n    (UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR == 11 && UV_VERSION_PATCH < 23)\nstatic void redisLibuvTimeout(uv_timer_t *timer, int status) {\n    (void)status; // unused\n#else\nstatic void redisLibuvTimeout(uv_timer_t *timer) {\n#endif\n    redisLibuvEvents *e = (redisLibuvEvents*)timer->data;\n    redisAsyncHandleTimeout(e->context);\n}\n\nstatic void redisLibuvSetTimeout(void *privdata, struct timeval tv) {\n    redisLibuvEvents* p = (redisLibuvEvents*)privdata;\n\n    uint64_t millsec = tv.tv_sec * 1000 + tv.tv_usec / 1000.0;\n    if (!p->timer.data) {\n        // timer is uninitialized\n        if (uv_timer_init(p->handle.loop, &p->timer) != 0) {\n            return;\n        }\n        p->timer.data = p;\n    }\n    // updates the timeout if the timer has already started\n    // or start the timer\n    uv_timer_start(&p->timer, redisLibuvTimeout, millsec, 0);\n}\n\nstatic void redisLibuvCleanup(void *privdata) {\n    redisLibuvEvents* p = (redisLibuvEvents*)privdata;\n\n    p->context = NULL; // indicate that context might no longer exist\n    if (p->timer.data) {\n        uv_close((uv_handle_t*)&p->timer, on_timer_close);\n    }\n    uv_close((uv_handle_t*)&p->handle, on_handle_close);\n}\n\n\nstatic int redisLibuvAttach(redisAsyncContext* ac, uv_loop_t* loop) {\n    redisContext *c = &(ac->c);\n\n    if (ac->ev.data != NULL) {\n        return REDIS_ERR;\n    }\n\n    ac->ev.addRead        = redisLibuvAddRead;\n    ac->ev.delRead        = redisLibuvDelRead;\n    ac->ev.addWrite       = redisLibuvAddWrite;\n    ac->ev.delWrite       = redisLibuvDelWrite;\n    ac->ev.cleanup        = redisLibuvCleanup;\n    ac->ev.scheduleTimer  = redisLibuvSetTimeout;\n\n    redisLibuvEvents* p = (redisLibuvEvents*)hi_malloc(sizeof(*p));\n    if (p == NULL)\n        return REDIS_ERR;\n\n    memset(p, 0, sizeof(*p));\n\n    if (uv_poll_init_socket(loop, &p->handle, c->fd) != 0) {\n        hi_free(p);\n        return REDIS_ERR;\n    }\n\n    ac->ev.data    = p;\n    p->handle.data = p;\n    p->context     = ac;\n\n    return REDIS_OK;\n}\n#endif\n"
  },
  {
    "path": "adapters/macosx.h",
    "content": "/*\n * Copyright (c) 2015 Дмитрий Бахвалов (Dmitry Bakhvalov)\n *\n * Permission for license update:\n *   https://github.com/redis/hiredis/issues/1271#issuecomment-2258225227\n *\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n *   * Redistributions of source code must retain the above copyright notice,\n *     this list of conditions and the following disclaimer.\n *   * Redistributions in binary form must reproduce the above copyright\n *     notice, this list of conditions and the following disclaimer in the\n *     documentation and/or other materials provided with the distribution.\n *   * Neither the name of Redis nor the names of its contributors may be used\n *     to endorse or promote products derived from this software without\n *     specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE\n * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n */\n\n#ifndef __HIREDIS_MACOSX_H__\n#define __HIREDIS_MACOSX_H__\n\n#include <CoreFoundation/CoreFoundation.h>\n\n#include \"../hiredis.h\"\n#include \"../async.h\"\n\ntypedef struct {\n    redisAsyncContext *context;\n    CFSocketRef socketRef;\n    CFRunLoopSourceRef sourceRef;\n} RedisRunLoop;\n\nstatic int freeRedisRunLoop(RedisRunLoop* redisRunLoop) {\n    if( redisRunLoop != NULL ) {\n        if( redisRunLoop->sourceRef != NULL ) {\n            CFRunLoopSourceInvalidate(redisRunLoop->sourceRef);\n            CFRelease(redisRunLoop->sourceRef);\n        }\n        if( redisRunLoop->socketRef != NULL ) {\n            CFSocketInvalidate(redisRunLoop->socketRef);\n            CFRelease(redisRunLoop->socketRef);\n        }\n        hi_free(redisRunLoop);\n    }\n    return REDIS_ERR;\n}\n\nstatic void redisMacOSAddRead(void *privdata) {\n    RedisRunLoop *redisRunLoop = (RedisRunLoop*)privdata;\n    CFSocketEnableCallBacks(redisRunLoop->socketRef, kCFSocketReadCallBack);\n}\n\nstatic void redisMacOSDelRead(void *privdata) {\n    RedisRunLoop *redisRunLoop = (RedisRunLoop*)privdata;\n    CFSocketDisableCallBacks(redisRunLoop->socketRef, kCFSocketReadCallBack);\n}\n\nstatic void redisMacOSAddWrite(void *privdata) {\n    RedisRunLoop *redisRunLoop = (RedisRunLoop*)privdata;\n    CFSocketEnableCallBacks(redisRunLoop->socketRef, kCFSocketWriteCallBack);\n}\n\nstatic void redisMacOSDelWrite(void *privdata) {\n    RedisRunLoop *redisRunLoop = (RedisRunLoop*)privdata;\n    CFSocketDisableCallBacks(redisRunLoop->socketRef, kCFSocketWriteCallBack);\n}\n\nstatic void redisMacOSCleanup(void *privdata) {\n    RedisRunLoop *redisRunLoop = (RedisRunLoop*)privdata;\n    freeRedisRunLoop(redisRunLoop);\n}\n\nstatic void redisMacOSAsyncCallback(CFSocketRef __unused s, CFSocketCallBackType callbackType, CFDataRef __unused address, const void __unused *data, void *info) {\n    redisAsyncContext* context = (redisAsyncContext*) info;\n\n    switch (callbackType) {\n        case kCFSocketReadCallBack:\n            redisAsyncHandleRead(context);\n            break;\n\n        case kCFSocketWriteCallBack:\n            redisAsyncHandleWrite(context);\n            break;\n\n        default:\n            break;\n    }\n}\n\nstatic int redisMacOSAttach(redisAsyncContext *redisAsyncCtx, CFRunLoopRef runLoop) {\n    redisContext *redisCtx = &(redisAsyncCtx->c);\n\n    /* Nothing should be attached when something is already attached */\n    if( redisAsyncCtx->ev.data != NULL ) return REDIS_ERR;\n\n    RedisRunLoop* redisRunLoop = (RedisRunLoop*) hi_calloc(1, sizeof(RedisRunLoop));\n    if (redisRunLoop == NULL)\n        return REDIS_ERR;\n\n    /* Setup redis stuff */\n    redisRunLoop->context = redisAsyncCtx;\n\n    redisAsyncCtx->ev.addRead  = redisMacOSAddRead;\n    redisAsyncCtx->ev.delRead  = redisMacOSDelRead;\n    redisAsyncCtx->ev.addWrite = redisMacOSAddWrite;\n    redisAsyncCtx->ev.delWrite = redisMacOSDelWrite;\n    redisAsyncCtx->ev.cleanup  = redisMacOSCleanup;\n    redisAsyncCtx->ev.data     = redisRunLoop;\n\n    /* Initialize and install read/write events */\n    CFSocketContext socketCtx = { 0, redisAsyncCtx, NULL, NULL, NULL };\n\n    redisRunLoop->socketRef = CFSocketCreateWithNative(NULL, redisCtx->fd,\n                                                       kCFSocketReadCallBack | kCFSocketWriteCallBack,\n                                                       redisMacOSAsyncCallback,\n                                                       &socketCtx);\n    if( !redisRunLoop->socketRef ) return freeRedisRunLoop(redisRunLoop);\n\n    redisRunLoop->sourceRef = CFSocketCreateRunLoopSource(NULL, redisRunLoop->socketRef, 0);\n    if( !redisRunLoop->sourceRef ) return freeRedisRunLoop(redisRunLoop);\n\n    CFRunLoopAddSource(runLoop, redisRunLoop->sourceRef, kCFRunLoopDefaultMode);\n\n    return REDIS_OK;\n}\n\n#endif\n\n"
  },
  {
    "path": "adapters/poll.h",
    "content": "\n#ifndef HIREDIS_POLL_H\n#define HIREDIS_POLL_H\n\n#include \"../async.h\"\n#include \"../sockcompat.h\"\n#include <string.h> // for memset\n#include <errno.h>\n\n/* Values to return from redisPollTick */\n#define REDIS_POLL_HANDLED_READ    1\n#define REDIS_POLL_HANDLED_WRITE   2\n#define REDIS_POLL_HANDLED_TIMEOUT 4\n\n/* An adapter to allow manual polling of the async context by checking the state\n * of the underlying file descriptor.  Useful in cases where there is no formal\n * IO event loop but regular ticking can be used, such as in game engines. */\n\ntypedef struct redisPollEvents {\n    redisAsyncContext *context;\n    redisFD fd;\n    char reading, writing;\n    char in_tick;\n    char deleted;\n    double deadline;\n} redisPollEvents;\n\nstatic double redisPollTimevalToDouble(struct timeval *tv) {\n    if (tv == NULL)\n        return 0.0;\n    return tv->tv_sec + tv->tv_usec / 1000000.00;\n}\n\nstatic double redisPollGetNow(void) {\n#ifndef _MSC_VER\n    struct timeval tv;\n    gettimeofday(&tv,NULL);\n    return redisPollTimevalToDouble(&tv);\n#else\n    FILETIME ft;\n    ULARGE_INTEGER li;\n    GetSystemTimeAsFileTime(&ft);\n    li.HighPart = ft.dwHighDateTime;\n    li.LowPart = ft.dwLowDateTime;\n    return (double)li.QuadPart * 1e-7;\n#endif\n}\n\n/* Poll for io, handling any pending callbacks.  The timeout argument can be\n * positive to wait for a maximum given time for IO, zero to poll, or negative\n * to wait forever */\nstatic int redisPollTick(redisAsyncContext *ac, double timeout) {\n    int reading, writing;\n    struct pollfd pfd;\n    int handled;\n    int ns;\n    int itimeout;\n\n    redisPollEvents *e = (redisPollEvents*)ac->ev.data;\n    if (!e)\n        return 0;\n\n    /* local flags, won't get changed during callbacks */\n    reading = e->reading;\n    writing = e->writing;\n    if (!reading && !writing)\n        return 0;\n\n    pfd.fd = e->fd;\n    pfd.events = 0;\n    if (reading)\n        pfd.events = POLLIN;   \n    if (writing)\n        pfd.events |= POLLOUT;\n\n    if (timeout >= 0.0) {\n        itimeout = (int)(timeout * 1000.0);\n    } else {\n        itimeout = -1;\n    }\n\n    ns = poll(&pfd, 1, itimeout);\n    if (ns < 0) {\n        /* ignore the EINTR error */\n        if (errno != EINTR)\n            return ns;\n        ns = 0;\n    }\n    \n    handled = 0;\n    e->in_tick = 1;\n    if (ns) {\n        if (reading && (pfd.revents & POLLIN)) {\n            redisAsyncHandleRead(ac);\n            handled |= REDIS_POLL_HANDLED_READ;\n        }\n        /* on Windows, connection failure is indicated with the Exception fdset.\n         * handle it the same as writable. */\n        if (writing && (pfd.revents & (POLLOUT | POLLERR))) {\n            /* context Read callback may have caused context to be deleted, e.g.\n               by doing an redisAsyncDisconnect() */\n            if (!e->deleted) {\n                redisAsyncHandleWrite(ac);\n                handled |= REDIS_POLL_HANDLED_WRITE;\n            }\n        }\n    }\n\n    /* perform timeouts */\n    if (!e->deleted && e->deadline != 0.0) {\n        double now = redisPollGetNow();\n        if (now >= e->deadline) {\n            /* deadline has passed.  disable timeout and perform callback */\n            e->deadline = 0.0;\n            redisAsyncHandleTimeout(ac);\n            handled |= REDIS_POLL_HANDLED_TIMEOUT;\n        }\n    }\n\n    /* do a delayed cleanup if required */\n    if (e->deleted)\n        hi_free(e);\n    else\n        e->in_tick = 0;\n\n    return handled;\n}\n\nstatic void redisPollAddRead(void *data) {\n    redisPollEvents *e = (redisPollEvents*)data;\n    e->reading = 1;\n}\n\nstatic void redisPollDelRead(void *data) {\n    redisPollEvents *e = (redisPollEvents*)data;\n    e->reading = 0;\n}\n\nstatic void redisPollAddWrite(void *data) {\n    redisPollEvents *e = (redisPollEvents*)data;\n    e->writing = 1;\n}\n\nstatic void redisPollDelWrite(void *data) {\n    redisPollEvents *e = (redisPollEvents*)data;\n    e->writing = 0;\n}\n\nstatic void redisPollCleanup(void *data) {\n    redisPollEvents *e = (redisPollEvents*)data;\n\n    /* if we are currently processing a tick, postpone deletion */\n    if (e->in_tick)\n        e->deleted = 1;\n    else\n        hi_free(e);\n}\n\nstatic void redisPollScheduleTimer(void *data, struct timeval tv)\n{\n    redisPollEvents *e = (redisPollEvents*)data;\n    double now = redisPollGetNow();\n    e->deadline = now + redisPollTimevalToDouble(&tv);\n}\n\nstatic int redisPollAttach(redisAsyncContext *ac) {\n    redisContext *c = &(ac->c);\n    redisPollEvents *e;\n\n    /* Nothing should be attached when something is already attached */\n    if (ac->ev.data != NULL)\n        return REDIS_ERR;\n\n    /* Create container for context and r/w events */\n    e = (redisPollEvents*)hi_malloc(sizeof(*e));\n    if (e == NULL)\n        return REDIS_ERR;\n    memset(e, 0, sizeof(*e));\n\n    e->context = ac;\n    e->fd = c->fd;\n    e->reading = e->writing = 0;\n    e->in_tick = e->deleted = 0;\n    e->deadline = 0.0;\n\n    /* Register functions to start/stop listening for events */\n    ac->ev.addRead = redisPollAddRead;\n    ac->ev.delRead = redisPollDelRead;\n    ac->ev.addWrite = redisPollAddWrite;\n    ac->ev.delWrite = redisPollDelWrite;\n    ac->ev.scheduleTimer = redisPollScheduleTimer;\n    ac->ev.cleanup = redisPollCleanup;\n    ac->ev.data = e;\n\n    return REDIS_OK;\n}\n#endif /* HIREDIS_POLL_H */\n"
  },
  {
    "path": "adapters/qt.h",
    "content": "/*-\n * Copyright (C) 2014 Pietro Cerutti <gahr@gahr.ch>\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n * SUCH DAMAGE.\n */\n\n#ifndef __HIREDIS_QT_H__\n#define __HIREDIS_QT_H__\n#include <QSocketNotifier>\n#include \"../async.h\"\n\nstatic void RedisQtAddRead(void *);\nstatic void RedisQtDelRead(void *);\nstatic void RedisQtAddWrite(void *);\nstatic void RedisQtDelWrite(void *);\nstatic void RedisQtCleanup(void *);\n\nclass RedisQtAdapter : public QObject {\n\n    Q_OBJECT\n\n    friend\n    void RedisQtAddRead(void * adapter) {\n        RedisQtAdapter * a = static_cast<RedisQtAdapter *>(adapter);\n        a->addRead();\n    }\n\n    friend\n    void RedisQtDelRead(void * adapter) {\n        RedisQtAdapter * a = static_cast<RedisQtAdapter *>(adapter);\n        a->delRead();\n    }\n\n    friend\n    void RedisQtAddWrite(void * adapter) {\n        RedisQtAdapter * a = static_cast<RedisQtAdapter *>(adapter);\n        a->addWrite();\n    }\n\n    friend\n    void RedisQtDelWrite(void * adapter) {\n        RedisQtAdapter * a = static_cast<RedisQtAdapter *>(adapter);\n        a->delWrite();\n    }\n\n    friend\n    void RedisQtCleanup(void * adapter) {\n        RedisQtAdapter * a = static_cast<RedisQtAdapter *>(adapter);\n        a->cleanup();\n    }\n\n    public:\n        RedisQtAdapter(QObject * parent = 0)\n            : QObject(parent), m_ctx(0), m_read(0), m_write(0) { }\n\n        ~RedisQtAdapter() {\n            if (m_ctx != 0) {\n                m_ctx->ev.data = NULL;\n            }\n        }\n\n        int setContext(redisAsyncContext * ac) {\n            if (ac->ev.data != NULL) {\n                return REDIS_ERR;\n            }\n            m_ctx = ac;\n            m_ctx->ev.data = this;\n            m_ctx->ev.addRead = RedisQtAddRead;\n            m_ctx->ev.delRead = RedisQtDelRead;\n            m_ctx->ev.addWrite = RedisQtAddWrite;\n            m_ctx->ev.delWrite = RedisQtDelWrite;\n            m_ctx->ev.cleanup = RedisQtCleanup;\n            return REDIS_OK;\n        }\n\n    private:\n        void addRead() {\n            if (m_read) return;\n            m_read = new QSocketNotifier(m_ctx->c.fd, QSocketNotifier::Read, 0);\n            connect(m_read, SIGNAL(activated(int)), this, SLOT(read()));\n        }\n\n        void delRead() {\n            if (!m_read) return;\n            delete m_read;\n            m_read = 0;\n        }\n\n        void addWrite() {\n            if (m_write) return;\n            m_write = new QSocketNotifier(m_ctx->c.fd, QSocketNotifier::Write, 0);\n            connect(m_write, SIGNAL(activated(int)), this, SLOT(write()));\n        }\n\n        void delWrite() {\n            if (!m_write) return;\n            delete m_write;\n            m_write = 0;\n        }\n\n        void cleanup() {\n            delRead();\n            delWrite();\n        }\n\n    private slots:\n        void read() { redisAsyncHandleRead(m_ctx); }\n        void write() { redisAsyncHandleWrite(m_ctx); }\n\n    private:\n        redisAsyncContext * m_ctx;\n        QSocketNotifier * m_read;\n        QSocketNotifier * m_write;\n};\n\n#endif /* !__HIREDIS_QT_H__ */\n"
  },
  {
    "path": "adapters/redismoduleapi.h",
    "content": "#ifndef __HIREDIS_REDISMODULEAPI_H__\n#define __HIREDIS_REDISMODULEAPI_H__\n\n#include \"redismodule.h\"\n\n#include \"../async.h\"\n#include \"../hiredis.h\"\n\n#include <sys/types.h>\n\ntypedef struct redisModuleEvents {\n    redisAsyncContext *context;\n    RedisModuleCtx *module_ctx;\n    int fd;\n    int reading, writing;\n    int timer_active;\n    RedisModuleTimerID timer_id;\n} redisModuleEvents;\n\nstatic inline void redisModuleReadEvent(int fd, void *privdata, int mask) {\n    (void) fd;\n    (void) mask;\n\n    redisModuleEvents *e = (redisModuleEvents*)privdata;\n    redisAsyncHandleRead(e->context);\n}\n\nstatic inline void redisModuleWriteEvent(int fd, void *privdata, int mask) {\n    (void) fd;\n    (void) mask;\n\n    redisModuleEvents *e = (redisModuleEvents*)privdata;\n    redisAsyncHandleWrite(e->context);\n}\n\nstatic inline void redisModuleAddRead(void *privdata) {\n    redisModuleEvents *e = (redisModuleEvents*)privdata;\n    if (!e->reading) {\n        e->reading = 1;\n        RedisModule_EventLoopAdd(e->fd, REDISMODULE_EVENTLOOP_READABLE, redisModuleReadEvent, e);\n    }\n}\n\nstatic inline void redisModuleDelRead(void *privdata) {\n    redisModuleEvents *e = (redisModuleEvents*)privdata;\n    if (e->reading) {\n        e->reading = 0;\n        RedisModule_EventLoopDel(e->fd, REDISMODULE_EVENTLOOP_READABLE);\n    }\n}\n\nstatic inline void redisModuleAddWrite(void *privdata) {\n    redisModuleEvents *e = (redisModuleEvents*)privdata;\n    if (!e->writing) {\n        e->writing = 1;\n        RedisModule_EventLoopAdd(e->fd, REDISMODULE_EVENTLOOP_WRITABLE, redisModuleWriteEvent, e);\n    }\n}\n\nstatic inline void redisModuleDelWrite(void *privdata) {\n    redisModuleEvents *e = (redisModuleEvents*)privdata;\n    if (e->writing) {\n        e->writing = 0;\n        RedisModule_EventLoopDel(e->fd, REDISMODULE_EVENTLOOP_WRITABLE);\n    }\n}\n\nstatic inline void redisModuleStopTimer(void *privdata) {\n    redisModuleEvents *e = (redisModuleEvents*)privdata;\n    if (e->timer_active) {\n        RedisModule_StopTimer(e->module_ctx, e->timer_id, NULL);\n    }\n    e->timer_active = 0;\n}\n\nstatic inline void redisModuleCleanup(void *privdata) {\n    redisModuleEvents *e = (redisModuleEvents*)privdata;\n    redisModuleDelRead(privdata);\n    redisModuleDelWrite(privdata);\n    redisModuleStopTimer(privdata);\n    hi_free(e);\n}\n\nstatic inline void redisModuleTimeout(RedisModuleCtx *ctx, void *privdata) {\n    (void) ctx;\n\n    redisModuleEvents *e = (redisModuleEvents*)privdata;\n    e->timer_active = 0;\n    redisAsyncHandleTimeout(e->context);\n}\n\nstatic inline void redisModuleSetTimeout(void *privdata, struct timeval tv) {\n    redisModuleEvents* e = (redisModuleEvents*)privdata;\n\n    redisModuleStopTimer(privdata);\n\n    mstime_t millis = tv.tv_sec * 1000 + tv.tv_usec / 1000.0;\n    e->timer_id = RedisModule_CreateTimer(e->module_ctx, millis, redisModuleTimeout, e);\n    e->timer_active = 1;\n}\n\n/* Check if Redis version is compatible with the adapter. */\nstatic inline int redisModuleCompatibilityCheck(void) {\n    if (!RedisModule_EventLoopAdd ||\n        !RedisModule_EventLoopDel ||\n        !RedisModule_CreateTimer ||\n        !RedisModule_StopTimer) {\n        return REDIS_ERR;\n    }\n    return REDIS_OK;\n}\n\nstatic inline int redisModuleAttach(redisAsyncContext *ac, RedisModuleCtx *module_ctx) {\n    redisContext *c = &(ac->c);\n    redisModuleEvents *e;\n\n    /* Nothing should be attached when something is already attached */\n    if (ac->ev.data != NULL)\n        return REDIS_ERR;\n\n    /* Create container for context and r/w events */\n    e = (redisModuleEvents*)hi_malloc(sizeof(*e));\n    if (e == NULL)\n        return REDIS_ERR;\n\n    e->context = ac;\n    e->module_ctx = module_ctx;\n    e->fd = c->fd;\n    e->reading = e->writing = 0;\n    e->timer_active = 0;\n\n    /* Register functions to start/stop listening for events */\n    ac->ev.addRead = redisModuleAddRead;\n    ac->ev.delRead = redisModuleDelRead;\n    ac->ev.addWrite = redisModuleAddWrite;\n    ac->ev.delWrite = redisModuleDelWrite;\n    ac->ev.cleanup = redisModuleCleanup;\n    ac->ev.scheduleTimer = redisModuleSetTimeout;\n    ac->ev.data = e;\n\n    return REDIS_OK;\n}\n\n#endif\n"
  },
  {
    "path": "alloc.c",
    "content": "/*\n * Copyright (c) 2020, Michael Grunder <michael dot grunder at gmail dot com>\n *\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n *   * Redistributions of source code must retain the above copyright notice,\n *     this list of conditions and the following disclaimer.\n *   * Redistributions in binary form must reproduce the above copyright\n *     notice, this list of conditions and the following disclaimer in the\n *     documentation and/or other materials provided with the distribution.\n *   * Neither the name of Redis nor the names of its contributors may be used\n *     to endorse or promote products derived from this software without\n *     specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE\n * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n */\n\n#include \"fmacros.h\"\n#include \"alloc.h\"\n#include <string.h>\n#include <stdlib.h>\n\nhiredisAllocFuncs hiredisAllocFns = {\n    .mallocFn = malloc,\n    .callocFn = calloc,\n    .reallocFn = realloc,\n    .strdupFn = strdup,\n    .freeFn = free,\n};\n\n/* Override hiredis' allocators with ones supplied by the user */\nhiredisAllocFuncs hiredisSetAllocators(hiredisAllocFuncs *override) {\n    hiredisAllocFuncs orig = hiredisAllocFns;\n\n    hiredisAllocFns = *override;\n\n    return orig;\n}\n\n/* Reset allocators to use libc defaults */\nvoid hiredisResetAllocators(void) {\n    hiredisAllocFns = (hiredisAllocFuncs) {\n        .mallocFn = malloc,\n        .callocFn = calloc,\n        .reallocFn = realloc,\n        .strdupFn = strdup,\n        .freeFn = free,\n    };\n}\n\n#ifdef _WIN32\n\nvoid *hi_malloc(size_t size) {\n    return hiredisAllocFns.mallocFn(size);\n}\n\nvoid *hi_calloc(size_t nmemb, size_t size) {\n    /* Overflow check as the user can specify any arbitrary allocator */\n    if (SIZE_MAX / size < nmemb)\n        return NULL;\n\n    return hiredisAllocFns.callocFn(nmemb, size);\n}\n\nvoid *hi_realloc(void *ptr, size_t size) {\n    return hiredisAllocFns.reallocFn(ptr, size);\n}\n\nchar *hi_strdup(const char *str) {\n    return hiredisAllocFns.strdupFn(str);\n}\n\nvoid hi_free(void *ptr) {\n    hiredisAllocFns.freeFn(ptr);\n}\n\n#endif\n"
  },
  {
    "path": "alloc.h",
    "content": "/*\n * Copyright (c) 2020, Michael Grunder <michael dot grunder at gmail dot com>\n *\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n *   * Redistributions of source code must retain the above copyright notice,\n *     this list of conditions and the following disclaimer.\n *   * Redistributions in binary form must reproduce the above copyright\n *     notice, this list of conditions and the following disclaimer in the\n *     documentation and/or other materials provided with the distribution.\n *   * Neither the name of Redis nor the names of its contributors may be used\n *     to endorse or promote products derived from this software without\n *     specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE\n * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n */\n\n#ifndef HIREDIS_ALLOC_H\n#define HIREDIS_ALLOC_H\n\n#include <stddef.h> /* for size_t */\n#include <stdint.h>\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n/* Structure pointing to our actually configured allocators */\ntypedef struct hiredisAllocFuncs {\n    void *(*mallocFn)(size_t);\n    void *(*callocFn)(size_t,size_t);\n    void *(*reallocFn)(void*,size_t);\n    char *(*strdupFn)(const char*);\n    void (*freeFn)(void*);\n} hiredisAllocFuncs;\n\nhiredisAllocFuncs hiredisSetAllocators(hiredisAllocFuncs *ha);\nvoid hiredisResetAllocators(void);\n\n#ifndef _WIN32\n\n/* Hiredis' configured allocator function pointer struct */\nextern hiredisAllocFuncs hiredisAllocFns;\n\nstatic inline void *hi_malloc(size_t size) {\n    return hiredisAllocFns.mallocFn(size);\n}\n\nstatic inline void *hi_calloc(size_t nmemb, size_t size) {\n    /* Overflow check as the user can specify any arbitrary allocator */\n    if (SIZE_MAX / size < nmemb)\n        return NULL;\n\n    return hiredisAllocFns.callocFn(nmemb, size);\n}\n\nstatic inline void *hi_realloc(void *ptr, size_t size) {\n    return hiredisAllocFns.reallocFn(ptr, size);\n}\n\nstatic inline char *hi_strdup(const char *str) {\n    return hiredisAllocFns.strdupFn(str);\n}\n\nstatic inline void hi_free(void *ptr) {\n    hiredisAllocFns.freeFn(ptr);\n}\n\n#else\n\nvoid *hi_malloc(size_t size);\nvoid *hi_calloc(size_t nmemb, size_t size);\nvoid *hi_realloc(void *ptr, size_t size);\nchar *hi_strdup(const char *str);\nvoid hi_free(void *ptr);\n\n#endif\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif /* HIREDIS_ALLOC_H */\n"
  },
  {
    "path": "appveyor.yml",
    "content": "# Appveyor configuration file for CI build of hiredis on Windows (under Cygwin)\nenvironment:\n    matrix:\n        - CYG_BASH: C:\\cygwin64\\bin\\bash\n          CC: gcc\n        - CYG_BASH: C:\\cygwin\\bin\\bash\n          CC: gcc\n          CFLAGS: -m32\n          CXXFLAGS: -m32\n          LDFLAGS: -m32\n\nclone_depth: 1\n\n# Attempt to ensure we don't try to convert line endings to Win32 CRLF as this will cause build to fail\ninit:\n    - git config --global core.autocrlf input\n\n# Install needed build dependencies\ninstall:\n    - '%CYG_BASH% -lc \"cygcheck -dc cygwin\"'\n\nbuild_script:\n    - 'echo building...'\n    - '%CYG_BASH% -lc \"cd $APPVEYOR_BUILD_FOLDER; exec 0</dev/null; mkdir build && cd build && cmake .. -G \\\"Unix Makefiles\\\" && make VERBOSE=1\"'\n"
  },
  {
    "path": "async.c",
    "content": "/*\n * Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com>\n * Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com>\n *\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n *   * Redistributions of source code must retain the above copyright notice,\n *     this list of conditions and the following disclaimer.\n *   * Redistributions in binary form must reproduce the above copyright\n *     notice, this list of conditions and the following disclaimer in the\n *     documentation and/or other materials provided with the distribution.\n *   * Neither the name of Redis nor the names of its contributors may be used\n *     to endorse or promote products derived from this software without\n *     specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE\n * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n */\n\n#include \"fmacros.h\"\n#include \"alloc.h\"\n#include <stdlib.h>\n#include <string.h>\n#ifndef _MSC_VER\n#include <strings.h>\n#endif\n#include <assert.h>\n#include <ctype.h>\n#include <errno.h>\n#include \"async.h\"\n#include \"net.h\"\n#include \"dict.c\"\n#include \"sds.h\"\n#include \"win32.h\"\n\n#include \"async_private.h\"\n\n#ifdef NDEBUG\n#undef assert\n#define assert(e) (void)(e)\n#endif\n\n/* Forward declarations of hiredis.c functions */\nint __redisAppendCommand(redisContext *c, const char *cmd, size_t len);\nvoid __redisSetError(redisContext *c, int type, const char *str);\n\n/* Functions managing dictionary of callbacks for pub/sub. */\nstatic unsigned int callbackHash(const void *key) {\n    return dictGenHashFunction((const unsigned char *)key,\n                               sdslen((const sds)key));\n}\n\nstatic void *callbackValDup(void *privdata, const void *src) {\n    ((void) privdata);\n    redisCallback *dup;\n\n    dup = hi_malloc(sizeof(*dup));\n    if (dup == NULL)\n        return NULL;\n\n    memcpy(dup,src,sizeof(*dup));\n    return dup;\n}\n\nstatic int callbackKeyCompare(void *privdata, const void *key1, const void *key2) {\n    int l1, l2;\n    ((void) privdata);\n\n    l1 = sdslen((const sds)key1);\n    l2 = sdslen((const sds)key2);\n    if (l1 != l2) return 0;\n    return memcmp(key1,key2,l1) == 0;\n}\n\nstatic void callbackKeyDestructor(void *privdata, void *key) {\n    ((void) privdata);\n    sdsfree((sds)key);\n}\n\nstatic void callbackValDestructor(void *privdata, void *val) {\n    ((void) privdata);\n    hi_free(val);\n}\n\nstatic dictType callbackDict = {\n    callbackHash,\n    NULL,\n    callbackValDup,\n    callbackKeyCompare,\n    callbackKeyDestructor,\n    callbackValDestructor\n};\n\nstatic redisAsyncContext *redisAsyncInitialize(redisContext *c) {\n    redisAsyncContext *ac;\n    dict *channels = NULL, *patterns = NULL;\n\n    channels = dictCreate(&callbackDict,NULL);\n    if (channels == NULL)\n        goto oom;\n\n    patterns = dictCreate(&callbackDict,NULL);\n    if (patterns == NULL)\n        goto oom;\n\n    ac = hi_realloc(c,sizeof(redisAsyncContext));\n    if (ac == NULL)\n        goto oom;\n\n    c = &(ac->c);\n\n    /* The regular connect functions will always set the flag REDIS_CONNECTED.\n     * For the async API, we want to wait until the first write event is\n     * received up before setting this flag, so reset it here. */\n    c->flags &= ~REDIS_CONNECTED;\n\n    ac->err = 0;\n    ac->errstr = NULL;\n    ac->data = NULL;\n    ac->dataCleanup = NULL;\n\n    ac->ev.data = NULL;\n    ac->ev.addRead = NULL;\n    ac->ev.delRead = NULL;\n    ac->ev.addWrite = NULL;\n    ac->ev.delWrite = NULL;\n    ac->ev.cleanup = NULL;\n    ac->ev.scheduleTimer = NULL;\n\n    ac->onConnect = NULL;\n    ac->onConnectNC = NULL;\n    ac->onDisconnect = NULL;\n\n    ac->replies.head = NULL;\n    ac->replies.tail = NULL;\n    ac->sub.replies.head = NULL;\n    ac->sub.replies.tail = NULL;\n    ac->sub.channels = channels;\n    ac->sub.patterns = patterns;\n    ac->sub.pending_unsubs = 0;\n\n    return ac;\noom:\n    if (channels) dictRelease(channels);\n    if (patterns) dictRelease(patterns);\n    return NULL;\n}\n\n/* We want the error field to be accessible directly instead of requiring\n * an indirection to the redisContext struct. */\nstatic void __redisAsyncCopyError(redisAsyncContext *ac) {\n    if (!ac)\n        return;\n\n    redisContext *c = &(ac->c);\n    ac->err = c->err;\n    ac->errstr = c->errstr;\n}\n\nredisAsyncContext *redisAsyncConnectWithOptions(const redisOptions *options) {\n    redisOptions myOptions = *options;\n    redisContext *c;\n    redisAsyncContext *ac;\n\n    /* Clear any erroneously set sync callback and flag that we don't want to\n     * use freeReplyObject by default. */\n    myOptions.push_cb = NULL;\n    myOptions.options |= REDIS_OPT_NO_PUSH_AUTOFREE;\n\n    myOptions.options |= REDIS_OPT_NONBLOCK;\n    c = redisConnectWithOptions(&myOptions);\n    if (c == NULL) {\n        return NULL;\n    }\n\n    ac = redisAsyncInitialize(c);\n    if (ac == NULL) {\n        redisFree(c);\n        return NULL;\n    }\n\n    /* Set any configured async push handler */\n    redisAsyncSetPushCallback(ac, myOptions.async_push_cb);\n\n    __redisAsyncCopyError(ac);\n    return ac;\n}\n\nredisAsyncContext *redisAsyncConnect(const char *ip, int port) {\n    redisOptions options = {0};\n    REDIS_OPTIONS_SET_TCP(&options, ip, port);\n    return redisAsyncConnectWithOptions(&options);\n}\n\nredisAsyncContext *redisAsyncConnectBind(const char *ip, int port,\n                                         const char *source_addr) {\n    redisOptions options = {0};\n    REDIS_OPTIONS_SET_TCP(&options, ip, port);\n    options.endpoint.tcp.source_addr = source_addr;\n    return redisAsyncConnectWithOptions(&options);\n}\n\nredisAsyncContext *redisAsyncConnectBindWithReuse(const char *ip, int port,\n                                                  const char *source_addr) {\n    redisOptions options = {0};\n    REDIS_OPTIONS_SET_TCP(&options, ip, port);\n    options.options |= REDIS_OPT_REUSEADDR;\n    options.endpoint.tcp.source_addr = source_addr;\n    return redisAsyncConnectWithOptions(&options);\n}\n\nredisAsyncContext *redisAsyncConnectUnix(const char *path) {\n    redisOptions options = {0};\n    REDIS_OPTIONS_SET_UNIX(&options, path);\n    return redisAsyncConnectWithOptions(&options);\n}\n\nstatic int\nredisAsyncSetConnectCallbackImpl(redisAsyncContext *ac, redisConnectCallback *fn,\n                                 redisConnectCallbackNC *fn_nc)\n{\n    /* If either are already set, this is an error */\n    if (ac->onConnect || ac->onConnectNC)\n        return REDIS_ERR;\n\n    if (fn) {\n        ac->onConnect = fn;\n    } else if (fn_nc) {\n        ac->onConnectNC = fn_nc;\n    }\n\n    /* The common way to detect an established connection is to wait for\n     * the first write event to be fired. This assumes the related event\n     * library functions are already set. */\n    _EL_ADD_WRITE(ac);\n\n    return REDIS_OK;\n}\n\nint redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn) {\n    return redisAsyncSetConnectCallbackImpl(ac, fn, NULL);\n}\n\nint redisAsyncSetConnectCallbackNC(redisAsyncContext *ac, redisConnectCallbackNC *fn) {\n    return redisAsyncSetConnectCallbackImpl(ac, NULL, fn);\n}\n\nint redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn) {\n    if (ac->onDisconnect == NULL) {\n        ac->onDisconnect = fn;\n        return REDIS_OK;\n    }\n    return REDIS_ERR;\n}\n\n/* Helper functions to push/shift callbacks */\nstatic int __redisPushCallback(redisCallbackList *list, redisCallback *source) {\n    redisCallback *cb;\n\n    /* Copy callback from stack to heap */\n    cb = hi_malloc(sizeof(*cb));\n    if (cb == NULL)\n        return REDIS_ERR_OOM;\n\n    if (source != NULL) {\n        memcpy(cb,source,sizeof(*cb));\n        cb->next = NULL;\n    }\n\n    /* Store callback in list */\n    if (list->head == NULL)\n        list->head = cb;\n    if (list->tail != NULL)\n        list->tail->next = cb;\n    list->tail = cb;\n    return REDIS_OK;\n}\n\nstatic int __redisShiftCallback(redisCallbackList *list, redisCallback *target) {\n    redisCallback *cb = list->head;\n    if (cb != NULL) {\n        list->head = cb->next;\n        if (cb == list->tail)\n            list->tail = NULL;\n\n        /* Copy callback from heap to stack */\n        if (target != NULL)\n            memcpy(target,cb,sizeof(*cb));\n        hi_free(cb);\n        return REDIS_OK;\n    }\n    return REDIS_ERR;\n}\n\nstatic void __redisRunCallback(redisAsyncContext *ac, redisCallback *cb, redisReply *reply) {\n    redisContext *c = &(ac->c);\n    if (cb->fn != NULL) {\n        c->flags |= REDIS_IN_CALLBACK;\n        cb->fn(ac,reply,cb->privdata);\n        c->flags &= ~REDIS_IN_CALLBACK;\n    }\n}\n\nstatic void __redisRunPushCallback(redisAsyncContext *ac, redisReply *reply) {\n    if (ac->push_cb != NULL) {\n        ac->c.flags |= REDIS_IN_CALLBACK;\n        ac->push_cb(ac, reply);\n        ac->c.flags &= ~REDIS_IN_CALLBACK;\n    }\n}\n\nstatic void __redisRunConnectCallback(redisAsyncContext *ac, int status)\n{\n    if (ac->onConnect == NULL && ac->onConnectNC == NULL)\n        return;\n\n    if (!(ac->c.flags & REDIS_IN_CALLBACK)) {\n        ac->c.flags |= REDIS_IN_CALLBACK;\n        if (ac->onConnect) {\n            ac->onConnect(ac, status);\n        } else {\n            ac->onConnectNC(ac, status);\n        }\n        ac->c.flags &= ~REDIS_IN_CALLBACK;\n    } else {\n        /* already in callback */\n        if (ac->onConnect) {\n            ac->onConnect(ac, status);\n        } else {\n            ac->onConnectNC(ac, status);\n        }\n    }\n}\n\nstatic void __redisRunDisconnectCallback(redisAsyncContext *ac, int status)\n{\n    if (ac->onDisconnect) {\n        if (!(ac->c.flags & REDIS_IN_CALLBACK)) {\n            ac->c.flags |= REDIS_IN_CALLBACK;\n            ac->onDisconnect(ac, status);\n            ac->c.flags &= ~REDIS_IN_CALLBACK;\n        } else {\n            /* already in callback */\n            ac->onDisconnect(ac, status);\n        }\n    }\n}\n\n/* Helper function to free the context. */\nstatic void __redisAsyncFree(redisAsyncContext *ac) {\n    redisContext *c = &(ac->c);\n    redisCallback cb;\n    dictIterator it;\n    dictEntry *de;\n\n    /* Execute pending callbacks with NULL reply. */\n    while (__redisShiftCallback(&ac->replies,&cb) == REDIS_OK)\n        __redisRunCallback(ac,&cb,NULL);\n    while (__redisShiftCallback(&ac->sub.replies,&cb) == REDIS_OK)\n        __redisRunCallback(ac,&cb,NULL);\n\n    /* Run subscription callbacks with NULL reply */\n    if (ac->sub.channels) {\n        dictInitIterator(&it,ac->sub.channels);\n        while ((de = dictNext(&it)) != NULL)\n            __redisRunCallback(ac,dictGetEntryVal(de),NULL);\n\n        dictRelease(ac->sub.channels);\n    }\n\n    if (ac->sub.patterns) {\n        dictInitIterator(&it,ac->sub.patterns);\n        while ((de = dictNext(&it)) != NULL)\n            __redisRunCallback(ac,dictGetEntryVal(de),NULL);\n\n        dictRelease(ac->sub.patterns);\n    }\n\n    /* Signal event lib to clean up */\n    _EL_CLEANUP(ac);\n\n    /* Execute disconnect callback. When redisAsyncFree() initiated destroying\n     * this context, the status will always be REDIS_OK. */\n    if (c->flags & REDIS_CONNECTED) {\n        int status = ac->err == 0 ? REDIS_OK : REDIS_ERR;\n        if (c->flags & REDIS_FREEING)\n            status = REDIS_OK;\n        __redisRunDisconnectCallback(ac, status);\n    }\n\n    if (ac->dataCleanup) {\n        ac->dataCleanup(ac->data);\n    }\n\n    /* Cleanup self */\n    redisFree(c);\n}\n\n/* Free the async context. When this function is called from a callback,\n * control needs to be returned to redisProcessCallbacks() before actual\n * free'ing. To do so, a flag is set on the context which is picked up by\n * redisProcessCallbacks(). Otherwise, the context is immediately free'd. */\nvoid redisAsyncFree(redisAsyncContext *ac) {\n    if (ac == NULL)\n        return;\n\n    redisContext *c = &(ac->c);\n\n    c->flags |= REDIS_FREEING;\n    if (!(c->flags & REDIS_IN_CALLBACK))\n        __redisAsyncFree(ac);\n}\n\n/* Helper function to make the disconnect happen and clean up. */\nvoid __redisAsyncDisconnect(redisAsyncContext *ac) {\n    redisContext *c = &(ac->c);\n\n    /* Make sure error is accessible if there is any */\n    __redisAsyncCopyError(ac);\n\n    if (ac->err == 0) {\n        /* For clean disconnects, there should be no pending callbacks. */\n        int ret = __redisShiftCallback(&ac->replies,NULL);\n        assert(ret == REDIS_ERR);\n    } else {\n        /* Disconnection is caused by an error, make sure that pending\n         * callbacks cannot call new commands. */\n        c->flags |= REDIS_DISCONNECTING;\n    }\n\n    /* cleanup event library on disconnect.\n     * this is safe to call multiple times */\n    _EL_CLEANUP(ac);\n\n    /* For non-clean disconnects, __redisAsyncFree() will execute pending\n     * callbacks with a NULL-reply. */\n    if (!(c->flags & REDIS_NO_AUTO_FREE)) {\n      __redisAsyncFree(ac);\n    }\n}\n\n/* Tries to do a clean disconnect from Redis, meaning it stops new commands\n * from being issued, but tries to flush the output buffer and execute\n * callbacks for all remaining replies. When this function is called from a\n * callback, there might be more replies and we can safely defer disconnecting\n * to redisProcessCallbacks(). Otherwise, we can only disconnect immediately\n * when there are no pending callbacks. */\nvoid redisAsyncDisconnect(redisAsyncContext *ac) {\n    redisContext *c = &(ac->c);\n    c->flags |= REDIS_DISCONNECTING;\n\n    /** unset the auto-free flag here, because disconnect undoes this */\n    c->flags &= ~REDIS_NO_AUTO_FREE;\n    if (!(c->flags & REDIS_IN_CALLBACK) && ac->replies.head == NULL)\n        __redisAsyncDisconnect(ac);\n}\n\nstatic int __redisGetSubscribeCallback(redisAsyncContext *ac, redisReply *reply, redisCallback *dstcb) {\n    redisContext *c = &(ac->c);\n    dict *callbacks;\n    redisCallback *cb = NULL;\n    dictEntry *de;\n    int pvariant;\n    char *stype;\n    sds sname = NULL;\n\n    /* Match reply with the expected format of a pushed message.\n     * The type and number of elements (3 to 4) are specified at:\n     * https://redis.io/docs/latest/develop/interact/pubsub/#format-of-pushed-messages */\n    if ((reply->type == REDIS_REPLY_ARRAY && !(c->flags & REDIS_SUPPORTS_PUSH) && reply->elements >= 3) ||\n        reply->type == REDIS_REPLY_PUSH) {\n        assert(reply->element[0]->type == REDIS_REPLY_STRING);\n        stype = reply->element[0]->str;\n        pvariant = (tolower(stype[0]) == 'p') ? 1 : 0;\n\n        if (pvariant)\n            callbacks = ac->sub.patterns;\n        else\n            callbacks = ac->sub.channels;\n\n        /* Locate the right callback */\n        if (reply->element[1]->type == REDIS_REPLY_STRING) {\n            sname = sdsnewlen(reply->element[1]->str,reply->element[1]->len);\n            if (sname == NULL) goto oom;\n\n            if ((de = dictFind(callbacks,sname)) != NULL) {\n                cb = dictGetEntryVal(de);\n                memcpy(dstcb,cb,sizeof(*dstcb));\n            }\n        }\n\n        /* If this is an subscribe reply decrease pending counter. */\n        if (strcasecmp(stype+pvariant,\"subscribe\") == 0) {\n            assert(cb != NULL);\n            cb->pending_subs -= 1;\n\n        } else if (strcasecmp(stype+pvariant,\"unsubscribe\") == 0) {\n            if (cb == NULL)\n                ac->sub.pending_unsubs -= 1;\n            else if (cb->pending_subs == 0)\n                dictDelete(callbacks,sname);\n\n            /* If this was the last unsubscribe message, revert to\n             * non-subscribe mode. */\n            assert(reply->element[2]->type == REDIS_REPLY_INTEGER);\n\n            /* Unset subscribed flag only when no pipelined pending subscribe\n             * or pending unsubscribe replies. */\n            if (reply->element[2]->integer == 0\n                && dictSize(ac->sub.channels) == 0\n                && dictSize(ac->sub.patterns) == 0\n                && ac->sub.pending_unsubs == 0) {\n                c->flags &= ~REDIS_SUBSCRIBED;\n\n                /* Move ongoing regular command callbacks. */\n                redisCallback cb;\n                while (__redisShiftCallback(&ac->sub.replies,&cb) == REDIS_OK) {\n                    __redisPushCallback(&ac->replies,&cb);\n                }\n            }\n        }\n        sdsfree(sname);\n    } else {\n        /* Shift callback for pending command in subscribed context. */\n        __redisShiftCallback(&ac->sub.replies,dstcb);\n    }\n    return REDIS_OK;\noom:\n    __redisSetError(&(ac->c), REDIS_ERR_OOM, \"Out of memory\");\n    __redisAsyncCopyError(ac);\n    return REDIS_ERR;\n}\n\n#define redisIsSpontaneousPushReply(r) \\\n    (redisIsPushReply(r) && !redisIsSubscribeReply(r))\n\nstatic int redisIsSubscribeReply(redisReply *reply) {\n    char *str;\n    size_t len, off;\n\n    /* We will always have at least one string with the subscribe/message type */\n    if (reply->elements < 1 || reply->element[0]->type != REDIS_REPLY_STRING ||\n        reply->element[0]->len < sizeof(\"message\") - 1)\n    {\n        return 0;\n    }\n\n    /* Get the string/len moving past 'p' if needed */\n    off = tolower(reply->element[0]->str[0]) == 'p';\n    str = reply->element[0]->str + off;\n    len = reply->element[0]->len - off;\n\n    return !strncasecmp(str, \"subscribe\", len) ||\n           !strncasecmp(str, \"message\", len) ||\n           !strncasecmp(str, \"unsubscribe\", len);\n}\n\nvoid redisProcessCallbacks(redisAsyncContext *ac) {\n    redisContext *c = &(ac->c);\n    void *reply = NULL;\n    int status;\n\n    while((status = redisGetReply(c,&reply)) == REDIS_OK) {\n        if (reply == NULL) {\n            /* When the connection is being disconnected and there are\n             * no more replies, this is the cue to really disconnect. */\n            if (c->flags & REDIS_DISCONNECTING && sdslen(c->obuf) == 0\n                && ac->replies.head == NULL) {\n                __redisAsyncDisconnect(ac);\n                return;\n            }\n            /* When the connection is not being disconnected, simply stop\n             * trying to get replies and wait for the next loop tick. */\n            break;\n        }\n\n        /* Keep track of push message support for subscribe handling */\n        if (redisIsPushReply(reply)) c->flags |= REDIS_SUPPORTS_PUSH;\n\n        /* Send any non-subscribe related PUSH messages to our PUSH handler\n         * while allowing subscribe related PUSH messages to pass through.\n         * This allows existing code to be backward compatible and work in\n         * either RESP2 or RESP3 mode. */\n        if (redisIsSpontaneousPushReply(reply)) {\n            __redisRunPushCallback(ac, reply);\n            c->reader->fn->freeObject(reply);\n            continue;\n        }\n\n        /* Even if the context is subscribed, pending regular\n         * callbacks will get a reply before pub/sub messages arrive. */\n        redisCallback cb = {NULL, NULL, 0, 0, NULL};\n        if (__redisShiftCallback(&ac->replies,&cb) != REDIS_OK) {\n            /*\n             * A spontaneous reply in a not-subscribed context can be the error\n             * reply that is sent when a new connection exceeds the maximum\n             * number of allowed connections on the server side.\n             *\n             * This is seen as an error instead of a regular reply because the\n             * server closes the connection after sending it.\n             *\n             * To prevent the error from being overwritten by an EOF error the\n             * connection is closed here. See issue #43.\n             *\n             * Another possibility is that the server is loading its dataset.\n             * In this case we also want to close the connection, and have the\n             * user wait until the server is ready to take our request.\n             */\n            if (((redisReply*)reply)->type == REDIS_REPLY_ERROR) {\n                c->err = REDIS_ERR_OTHER;\n                snprintf(c->errstr,sizeof(c->errstr),\"%s\",((redisReply*)reply)->str);\n                c->reader->fn->freeObject(reply);\n                __redisAsyncDisconnect(ac);\n                return;\n            }\n            /* No more regular callbacks and no errors, the context *must* be subscribed. */\n            assert(c->flags & REDIS_SUBSCRIBED);\n            if (c->flags & REDIS_SUBSCRIBED)\n                __redisGetSubscribeCallback(ac,reply,&cb);\n        }\n\n        if (cb.fn != NULL) {\n            __redisRunCallback(ac,&cb,reply);\n            if (!(c->flags & REDIS_NO_AUTO_FREE_REPLIES)){\n                c->reader->fn->freeObject(reply);\n            }\n\n            /* Proceed with free'ing when redisAsyncFree() was called. */\n            if (c->flags & REDIS_FREEING) {\n                __redisAsyncFree(ac);\n                return;\n            }\n        } else {\n            /* No callback for this reply. This can either be a NULL callback,\n             * or there were no callbacks to begin with. Either way, don't\n             * abort with an error, but simply ignore it because the client\n             * doesn't know what the server will spit out over the wire. */\n            c->reader->fn->freeObject(reply);\n        }\n\n        /* If in monitor mode, repush the callback */\n        if (c->flags & REDIS_MONITORING) {\n            __redisPushCallback(&ac->replies,&cb);\n        }\n    }\n\n    /* Disconnect when there was an error reading the reply */\n    if (status != REDIS_OK)\n        __redisAsyncDisconnect(ac);\n}\n\nstatic void __redisAsyncHandleConnectFailure(redisAsyncContext *ac) {\n    __redisRunConnectCallback(ac, REDIS_ERR);\n    __redisAsyncDisconnect(ac);\n}\n\n/* Internal helper function to detect socket status the first time a read or\n * write event fires. When connecting was not successful, the connect callback\n * is called with a REDIS_ERR status and the context is free'd. */\nstatic int __redisAsyncHandleConnect(redisAsyncContext *ac) {\n    int completed = 0;\n    redisContext *c = &(ac->c);\n\n    if (redisCheckConnectDone(c, &completed) == REDIS_ERR) {\n        /* Error! */\n        if (redisCheckSocketError(c) == REDIS_ERR)\n            __redisAsyncCopyError(ac);\n        __redisAsyncHandleConnectFailure(ac);\n        return REDIS_ERR;\n    } else if (completed == 1) {\n        /* connected! */\n        if (c->connection_type == REDIS_CONN_TCP &&\n            redisSetTcpNoDelay(c) == REDIS_ERR) {\n            __redisAsyncHandleConnectFailure(ac);\n            return REDIS_ERR;\n        }\n\n        /* flag us as fully connect, but allow the callback\n         * to disconnect.  For that reason, permit the function\n         * to delete the context here after callback return.\n         */\n        c->flags |= REDIS_CONNECTED;\n        __redisRunConnectCallback(ac, REDIS_OK);\n        if ((ac->c.flags & REDIS_DISCONNECTING)) {\n            redisAsyncDisconnect(ac);\n            return REDIS_ERR;\n        } else if ((ac->c.flags & REDIS_FREEING)) {\n            redisAsyncFree(ac);\n            return REDIS_ERR;\n        }\n        return REDIS_OK;\n    } else {\n        return REDIS_OK;\n    }\n}\n\nvoid redisAsyncRead(redisAsyncContext *ac) {\n    redisContext *c = &(ac->c);\n\n    if (redisBufferRead(c) == REDIS_ERR) {\n        __redisAsyncDisconnect(ac);\n    } else {\n        /* Always re-schedule reads */\n        _EL_ADD_READ(ac);\n        redisProcessCallbacks(ac);\n    }\n}\n\n/* This function should be called when the socket is readable.\n * It processes all replies that can be read and executes their callbacks.\n */\nvoid redisAsyncHandleRead(redisAsyncContext *ac) {\n    redisContext *c = &(ac->c);\n    /* must not be called from a callback */\n    assert(!(c->flags & REDIS_IN_CALLBACK));\n\n    if (!(c->flags & REDIS_CONNECTED)) {\n        /* Abort connect was not successful. */\n        if (__redisAsyncHandleConnect(ac) != REDIS_OK)\n            return;\n        /* Try again later when the context is still not connected. */\n        if (!(c->flags & REDIS_CONNECTED))\n            return;\n    }\n\n    c->funcs->async_read(ac);\n}\n\nvoid redisAsyncWrite(redisAsyncContext *ac) {\n    redisContext *c = &(ac->c);\n    int done = 0;\n\n    if (redisBufferWrite(c,&done) == REDIS_ERR) {\n        __redisAsyncDisconnect(ac);\n    } else {\n        /* Continue writing when not done, stop writing otherwise */\n        if (!done)\n            _EL_ADD_WRITE(ac);\n        else\n            _EL_DEL_WRITE(ac);\n\n        /* Always schedule reads after writes */\n        _EL_ADD_READ(ac);\n    }\n}\n\nvoid redisAsyncHandleWrite(redisAsyncContext *ac) {\n    redisContext *c = &(ac->c);\n    /* must not be called from a callback */\n    assert(!(c->flags & REDIS_IN_CALLBACK));\n\n    if (!(c->flags & REDIS_CONNECTED)) {\n        /* Abort connect was not successful. */\n        if (__redisAsyncHandleConnect(ac) != REDIS_OK)\n            return;\n        /* Try again later when the context is still not connected. */\n        if (!(c->flags & REDIS_CONNECTED))\n            return;\n    }\n\n    c->funcs->async_write(ac);\n}\n\nvoid redisAsyncHandleTimeout(redisAsyncContext *ac) {\n    redisContext *c = &(ac->c);\n    redisCallback cb;\n    /* must not be called from a callback */\n    assert(!(c->flags & REDIS_IN_CALLBACK));\n\n    if ((c->flags & REDIS_CONNECTED)) {\n        if (ac->replies.head == NULL && ac->sub.replies.head == NULL) {\n            /* Nothing to do - just an idle timeout */\n            return;\n        }\n\n        if (!ac->c.command_timeout ||\n            (!ac->c.command_timeout->tv_sec && !ac->c.command_timeout->tv_usec)) {\n            /* A belated connect timeout arriving, ignore */\n            return;\n        }\n    }\n\n    if (!c->err) {\n        __redisSetError(c, REDIS_ERR_TIMEOUT, \"Timeout\");\n        __redisAsyncCopyError(ac);\n    }\n\n    if (!(c->flags & REDIS_CONNECTED)) {\n        __redisRunConnectCallback(ac, REDIS_ERR);\n    }\n\n    while (__redisShiftCallback(&ac->replies, &cb) == REDIS_OK) {\n        __redisRunCallback(ac, &cb, NULL);\n    }\n\n    /**\n     * TODO: Don't automatically sever the connection,\n     * rather, allow to ignore <x> responses before the queue is clear\n     */\n    __redisAsyncDisconnect(ac);\n}\n\n/* Sets a pointer to the first argument and its length starting at p. Returns\n * the number of bytes to skip to get to the following argument. */\nstatic const char *nextArgument(const char *start, const char **str, size_t *len) {\n    const char *p = start;\n    if (p[0] != '$') {\n        p = strchr(p,'$');\n        if (p == NULL) return NULL;\n    }\n\n    *len = (int)strtol(p+1,NULL,10);\n    p = strchr(p,'\\r');\n    assert(p);\n    *str = p+2;\n    return p+2+(*len)+2;\n}\n\n/* Helper function for the redisAsyncCommand* family of functions. Writes a\n * formatted command to the output buffer and registers the provided callback\n * function with the context. */\nstatic int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *cmd, size_t len) {\n    redisContext *c = &(ac->c);\n    redisCallback cb;\n    struct dict *cbdict;\n    dictIterator it;\n    dictEntry *de;\n    redisCallback *existcb;\n    int pvariant, hasnext;\n    const char *cstr, *astr;\n    size_t clen, alen;\n    const char *p;\n    sds sname;\n    int ret;\n\n    /* Don't accept new commands when the connection is about to be closed. */\n    if (c->flags & (REDIS_DISCONNECTING | REDIS_FREEING)) return REDIS_ERR;\n\n    /* Setup callback */\n    cb.fn = fn;\n    cb.privdata = privdata;\n    cb.pending_subs = 1;\n    cb.unsubscribe_sent = 0;\n\n    /* Find out which command will be appended. */\n    p = nextArgument(cmd,&cstr,&clen);\n    assert(p != NULL);\n    hasnext = (p[0] == '$');\n    pvariant = (tolower(cstr[0]) == 'p') ? 1 : 0;\n    cstr += pvariant;\n    clen -= pvariant;\n\n    if (hasnext && strncasecmp(cstr,\"subscribe\\r\\n\",11) == 0) {\n        c->flags |= REDIS_SUBSCRIBED;\n\n        /* Add every channel/pattern to the list of subscription callbacks. */\n        while ((p = nextArgument(p,&astr,&alen)) != NULL) {\n            sname = sdsnewlen(astr,alen);\n            if (sname == NULL)\n                goto oom;\n\n            if (pvariant)\n                cbdict = ac->sub.patterns;\n            else\n                cbdict = ac->sub.channels;\n\n            de = dictFind(cbdict,sname);\n\n            if (de != NULL) {\n                existcb = dictGetEntryVal(de);\n                cb.pending_subs = existcb->pending_subs + 1;\n            }\n\n            ret = dictReplace(cbdict,sname,&cb);\n\n            if (ret == 0) sdsfree(sname);\n        }\n    } else if (strncasecmp(cstr,\"unsubscribe\\r\\n\",13) == 0) {\n        /* It is only useful to call (P)UNSUBSCRIBE when the context is\n         * subscribed to one or more channels or patterns. */\n        if (!(c->flags & REDIS_SUBSCRIBED)) return REDIS_ERR;\n\n        if (pvariant)\n            cbdict = ac->sub.patterns;\n        else\n            cbdict = ac->sub.channels;\n\n        if (hasnext) {\n            /* Send an unsubscribe with specific channels/patterns.\n             * Bookkeeping the number of expected replies */\n            while ((p = nextArgument(p,&astr,&alen)) != NULL) {\n                sname = sdsnewlen(astr,alen);\n                if (sname == NULL)\n                    goto oom;\n\n                de = dictFind(cbdict,sname);\n                if (de != NULL) {\n                    existcb = dictGetEntryVal(de);\n                    if (existcb->unsubscribe_sent == 0)\n                        existcb->unsubscribe_sent = 1;\n                    else\n                        /* Already sent, reply to be ignored */\n                        ac->sub.pending_unsubs += 1;\n                } else {\n                    /* Not subscribed to, reply to be ignored */\n                    ac->sub.pending_unsubs += 1;\n                }\n                sdsfree(sname);\n            }\n        } else {\n            /* Send an unsubscribe without specific channels/patterns.\n             * Bookkeeping the number of expected replies */\n            int no_subs = 1;\n            dictInitIterator(&it,cbdict);\n            while ((de = dictNext(&it)) != NULL) {\n                existcb = dictGetEntryVal(de);\n                if (existcb->unsubscribe_sent == 0) {\n                    existcb->unsubscribe_sent = 1;\n                    no_subs = 0;\n                }\n            }\n            /* Unsubscribing to all channels/patterns, where none is\n             * subscribed to, results in a single reply to be ignored. */\n            if (no_subs == 1)\n                ac->sub.pending_unsubs += 1;\n        }\n\n        /* (P)UNSUBSCRIBE does not have its own response: every channel or\n         * pattern that is unsubscribed will receive a message. This means we\n         * should not append a callback function for this command. */\n    } else if (strncasecmp(cstr,\"monitor\\r\\n\",9) == 0) {\n        /* Set monitor flag and push callback */\n        c->flags |= REDIS_MONITORING;\n        if (__redisPushCallback(&ac->replies,&cb) != REDIS_OK)\n            goto oom;\n    } else {\n        if (c->flags & REDIS_SUBSCRIBED) {\n            if (__redisPushCallback(&ac->sub.replies,&cb) != REDIS_OK)\n                goto oom;\n        } else {\n            if (__redisPushCallback(&ac->replies,&cb) != REDIS_OK)\n                goto oom;\n        }\n    }\n\n    __redisAppendCommand(c,cmd,len);\n\n    /* Always schedule a write when the write buffer is non-empty */\n    _EL_ADD_WRITE(ac);\n\n    return REDIS_OK;\noom:\n    __redisSetError(&(ac->c), REDIS_ERR_OOM, \"Out of memory\");\n    __redisAsyncCopyError(ac);\n    return REDIS_ERR;\n}\n\nint redisvAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, va_list ap) {\n    char *cmd;\n    int len;\n    int status;\n    len = redisvFormatCommand(&cmd,format,ap);\n\n    /* We don't want to pass -1 or -2 to future functions as a length. */\n    if (len < 0)\n        return REDIS_ERR;\n\n    status = __redisAsyncCommand(ac,fn,privdata,cmd,len);\n    hi_free(cmd);\n    return status;\n}\n\nint redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, ...) {\n    va_list ap;\n    int status;\n    va_start(ap,format);\n    status = redisvAsyncCommand(ac,fn,privdata,format,ap);\n    va_end(ap);\n    return status;\n}\n\nint redisAsyncCommandArgv(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen) {\n    sds cmd;\n    long long len;\n    int status;\n    len = redisFormatSdsCommandArgv(&cmd,argc,argv,argvlen);\n    if (len < 0)\n        return REDIS_ERR;\n    status = __redisAsyncCommand(ac,fn,privdata,cmd,len);\n    sdsfree(cmd);\n    return status;\n}\n\nint redisAsyncFormattedCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *cmd, size_t len) {\n    int status = __redisAsyncCommand(ac,fn,privdata,cmd,len);\n    return status;\n}\n\nredisAsyncPushFn *redisAsyncSetPushCallback(redisAsyncContext *ac, redisAsyncPushFn *fn) {\n    redisAsyncPushFn *old = ac->push_cb;\n    ac->push_cb = fn;\n    return old;\n}\n\nint redisAsyncSetTimeout(redisAsyncContext *ac, struct timeval tv) {\n    if (!ac->c.command_timeout) {\n        ac->c.command_timeout = hi_calloc(1, sizeof(tv));\n        if (ac->c.command_timeout == NULL) {\n            __redisSetError(&ac->c, REDIS_ERR_OOM, \"Out of memory\");\n            __redisAsyncCopyError(ac);\n            return REDIS_ERR;\n        }\n    }\n\n    if (tv.tv_sec != ac->c.command_timeout->tv_sec ||\n        tv.tv_usec != ac->c.command_timeout->tv_usec)\n    {\n        *ac->c.command_timeout = tv;\n    }\n\n    return REDIS_OK;\n}\n"
  },
  {
    "path": "async.h",
    "content": "/*\n * Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com>\n * Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com>\n *\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n *   * Redistributions of source code must retain the above copyright notice,\n *     this list of conditions and the following disclaimer.\n *   * Redistributions in binary form must reproduce the above copyright\n *     notice, this list of conditions and the following disclaimer in the\n *     documentation and/or other materials provided with the distribution.\n *   * Neither the name of Redis nor the names of its contributors may be used\n *     to endorse or promote products derived from this software without\n *     specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE\n * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n */\n\n#ifndef __HIREDIS_ASYNC_H\n#define __HIREDIS_ASYNC_H\n#include \"hiredis.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\nstruct redisAsyncContext; /* need forward declaration of redisAsyncContext */\nstruct dict; /* dictionary header is included in async.c */\n\n/* Reply callback prototype and container */\ntypedef void (redisCallbackFn)(struct redisAsyncContext*, void*, void*);\ntypedef struct redisCallback {\n    struct redisCallback *next; /* simple singly linked list */\n    redisCallbackFn *fn;\n    int pending_subs;\n    int unsubscribe_sent;\n    void *privdata;\n} redisCallback;\n\n/* List of callbacks for either regular replies or pub/sub */\ntypedef struct redisCallbackList {\n    redisCallback *head, *tail;\n} redisCallbackList;\n\n/* Connection callback prototypes */\ntypedef void (redisDisconnectCallback)(const struct redisAsyncContext*, int status);\ntypedef void (redisConnectCallback)(const struct redisAsyncContext*, int status);\ntypedef void (redisConnectCallbackNC)(struct redisAsyncContext *, int status);\ntypedef void(redisTimerCallback)(void *timer, void *privdata);\n\n/* Context for an async connection to Redis */\ntypedef struct redisAsyncContext {\n    /* Hold the regular context, so it can be realloc'ed. */\n    redisContext c;\n\n    /* Setup error flags so they can be used directly. */\n    int err;\n    char *errstr;\n\n    /* Not used by hiredis */\n    void *data;\n    void (*dataCleanup)(void *privdata);\n\n    /* Event library data and hooks */\n    struct {\n        void *data;\n\n        /* Hooks that are called when the library expects to start\n         * reading/writing. These functions should be idempotent. */\n        void (*addRead)(void *privdata);\n        void (*delRead)(void *privdata);\n        void (*addWrite)(void *privdata);\n        void (*delWrite)(void *privdata);\n        void (*cleanup)(void *privdata);\n        void (*scheduleTimer)(void *privdata, struct timeval tv);\n    } ev;\n\n    /* Called when either the connection is terminated due to an error or per\n     * user request. The status is set accordingly (REDIS_OK, REDIS_ERR). */\n    redisDisconnectCallback *onDisconnect;\n\n    /* Called when the first write event was received. */\n    redisConnectCallback *onConnect;\n    redisConnectCallbackNC *onConnectNC;\n\n    /* Regular command callbacks */\n    redisCallbackList replies;\n\n    /* Address used for connect() */\n    struct sockaddr *saddr;\n    size_t addrlen;\n\n    /* Subscription callbacks */\n    struct {\n        redisCallbackList replies;\n        struct dict *channels;\n        struct dict *patterns;\n        int pending_unsubs;\n    } sub;\n\n    /* Any configured RESP3 PUSH handler */\n    redisAsyncPushFn *push_cb;\n} redisAsyncContext;\n\n/* Functions that proxy to hiredis */\nredisAsyncContext *redisAsyncConnectWithOptions(const redisOptions *options);\nredisAsyncContext *redisAsyncConnect(const char *ip, int port);\nredisAsyncContext *redisAsyncConnectBind(const char *ip, int port, const char *source_addr);\nredisAsyncContext *redisAsyncConnectBindWithReuse(const char *ip, int port,\n                                                  const char *source_addr);\nredisAsyncContext *redisAsyncConnectUnix(const char *path);\nint redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn);\nint redisAsyncSetConnectCallbackNC(redisAsyncContext *ac, redisConnectCallbackNC *fn);\nint redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn);\n\nredisAsyncPushFn *redisAsyncSetPushCallback(redisAsyncContext *ac, redisAsyncPushFn *fn);\nint redisAsyncSetTimeout(redisAsyncContext *ac, struct timeval tv);\nvoid redisAsyncDisconnect(redisAsyncContext *ac);\nvoid redisAsyncFree(redisAsyncContext *ac);\n\n/* Handle read/write events */\nvoid redisAsyncHandleRead(redisAsyncContext *ac);\nvoid redisAsyncHandleWrite(redisAsyncContext *ac);\nvoid redisAsyncHandleTimeout(redisAsyncContext *ac);\nvoid redisAsyncRead(redisAsyncContext *ac);\nvoid redisAsyncWrite(redisAsyncContext *ac);\n\n/* Command functions for an async context. Write the command to the\n * output buffer and register the provided callback. */\nint redisvAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, va_list ap);\nint redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, ...);\nint redisAsyncCommandArgv(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen);\nint redisAsyncFormattedCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *cmd, size_t len);\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif\n"
  },
  {
    "path": "async_private.h",
    "content": "/*\n * Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com>\n * Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com>\n *\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n *   * Redistributions of source code must retain the above copyright notice,\n *     this list of conditions and the following disclaimer.\n *   * Redistributions in binary form must reproduce the above copyright\n *     notice, this list of conditions and the following disclaimer in the\n *     documentation and/or other materials provided with the distribution.\n *   * Neither the name of Redis nor the names of its contributors may be used\n *     to endorse or promote products derived from this software without\n *     specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE\n * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n */\n\n#ifndef __HIREDIS_ASYNC_PRIVATE_H\n#define __HIREDIS_ASYNC_PRIVATE_H\n\n#define _EL_ADD_READ(ctx)                                         \\\n    do {                                                          \\\n        refreshTimeout(ctx);                                      \\\n        if ((ctx)->ev.addRead) (ctx)->ev.addRead((ctx)->ev.data); \\\n    } while (0)\n#define _EL_DEL_READ(ctx) do { \\\n        if ((ctx)->ev.delRead) (ctx)->ev.delRead((ctx)->ev.data); \\\n    } while(0)\n#define _EL_ADD_WRITE(ctx)                                          \\\n    do {                                                            \\\n        refreshTimeout(ctx);                                        \\\n        if ((ctx)->ev.addWrite) (ctx)->ev.addWrite((ctx)->ev.data); \\\n    } while (0)\n#define _EL_DEL_WRITE(ctx) do { \\\n        if ((ctx)->ev.delWrite) (ctx)->ev.delWrite((ctx)->ev.data); \\\n    } while(0)\n#define _EL_CLEANUP(ctx) do { \\\n        if ((ctx)->ev.cleanup) (ctx)->ev.cleanup((ctx)->ev.data); \\\n        ctx->ev.cleanup = NULL; \\\n    } while(0)\n\nstatic inline void refreshTimeout(redisAsyncContext *ctx) {\n    #define REDIS_TIMER_ISSET(tvp) \\\n        (tvp && ((tvp)->tv_sec || (tvp)->tv_usec))\n\n    #define REDIS_EL_TIMER(ac, tvp) \\\n        if ((ac)->ev.scheduleTimer && REDIS_TIMER_ISSET(tvp)) { \\\n            (ac)->ev.scheduleTimer((ac)->ev.data, *(tvp)); \\\n        }\n\n    if (ctx->c.flags & REDIS_CONNECTED) {\n        REDIS_EL_TIMER(ctx, ctx->c.command_timeout);\n    } else {\n        REDIS_EL_TIMER(ctx, ctx->c.connect_timeout);\n    }\n}\n\nvoid __redisAsyncDisconnect(redisAsyncContext *ac);\nvoid redisProcessCallbacks(redisAsyncContext *ac);\n\n#endif  /* __HIREDIS_ASYNC_PRIVATE_H */\n"
  },
  {
    "path": "dict.c",
    "content": "/* Hash table implementation.\n *\n * This file implements in memory hash tables with insert/del/replace/find/\n * get-random-element operations. Hash tables will auto resize if needed\n * tables of power of two in size are used, collisions are handled by\n * chaining. See the source code for more information... :)\n *\n * Copyright (c) 2006-2010, Salvatore Sanfilippo <antirez at gmail dot com>\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n *   * Redistributions of source code must retain the above copyright notice,\n *     this list of conditions and the following disclaimer.\n *   * Redistributions in binary form must reproduce the above copyright\n *     notice, this list of conditions and the following disclaimer in the\n *     documentation and/or other materials provided with the distribution.\n *   * Neither the name of Redis nor the names of its contributors may be used\n *     to endorse or promote products derived from this software without\n *     specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE\n * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n */\n\n#include \"fmacros.h\"\n#include \"alloc.h\"\n#include <stdlib.h>\n#include <assert.h>\n#include <limits.h>\n#include \"dict.h\"\n\n/* -------------------------- private prototypes ---------------------------- */\n\nstatic int _dictExpandIfNeeded(dict *ht);\nstatic unsigned long _dictNextPower(unsigned long size);\nstatic int _dictKeyIndex(dict *ht, const void *key);\nstatic int _dictInit(dict *ht, dictType *type, void *privDataPtr);\n\n/* -------------------------- hash functions -------------------------------- */\n\n/* Generic hash function (a popular one from Bernstein).\n * I tested a few and this was the best. */\nstatic unsigned int dictGenHashFunction(const unsigned char *buf, int len) {\n    unsigned int hash = 5381;\n\n    while (len--)\n        hash = ((hash << 5) + hash) + (*buf++); /* hash * 33 + c */\n    return hash;\n}\n\n/* ----------------------------- API implementation ------------------------- */\n\n/* Reset an hashtable already initialized with ht_init().\n * NOTE: This function should only called by ht_destroy(). */\nstatic void _dictReset(dict *ht) {\n    ht->table = NULL;\n    ht->size = 0;\n    ht->sizemask = 0;\n    ht->used = 0;\n}\n\n/* Create a new hash table */\nstatic dict *dictCreate(dictType *type, void *privDataPtr) {\n    dict *ht = hi_malloc(sizeof(*ht));\n    if (ht == NULL)\n        return NULL;\n\n    _dictInit(ht,type,privDataPtr);\n    return ht;\n}\n\n/* Initialize the hash table */\nstatic int _dictInit(dict *ht, dictType *type, void *privDataPtr) {\n    _dictReset(ht);\n    ht->type = type;\n    ht->privdata = privDataPtr;\n    return DICT_OK;\n}\n\n/* Expand or create the hashtable */\nstatic int dictExpand(dict *ht, unsigned long size) {\n    dict n; /* the new hashtable */\n    unsigned long realsize = _dictNextPower(size), i;\n\n    /* the size is invalid if it is smaller than the number of\n     * elements already inside the hashtable */\n    if (ht->used > size)\n        return DICT_ERR;\n\n    _dictInit(&n, ht->type, ht->privdata);\n    n.size = realsize;\n    n.sizemask = realsize-1;\n    n.table = hi_calloc(realsize,sizeof(dictEntry*));\n    if (n.table == NULL)\n        return DICT_ERR;\n\n    /* Copy all the elements from the old to the new table:\n     * note that if the old hash table is empty ht->size is zero,\n     * so dictExpand just creates an hash table. */\n    n.used = ht->used;\n    for (i = 0; i < ht->size && ht->used > 0; i++) {\n        dictEntry *he, *nextHe;\n\n        if (ht->table[i] == NULL) continue;\n\n        /* For each hash entry on this slot... */\n        he = ht->table[i];\n        while(he) {\n            unsigned int h;\n\n            nextHe = he->next;\n            /* Get the new element index */\n            h = dictHashKey(ht, he->key) & n.sizemask;\n            he->next = n.table[h];\n            n.table[h] = he;\n            ht->used--;\n            /* Pass to the next element */\n            he = nextHe;\n        }\n    }\n    assert(ht->used == 0);\n    hi_free(ht->table);\n\n    /* Remap the new hashtable in the old */\n    *ht = n;\n    return DICT_OK;\n}\n\n/* Add an element to the target hash table */\nstatic int dictAdd(dict *ht, void *key, void *val) {\n    int index;\n    dictEntry *entry;\n\n    /* Get the index of the new element, or -1 if\n     * the element already exists. */\n    if ((index = _dictKeyIndex(ht, key)) == -1)\n        return DICT_ERR;\n\n    /* Allocates the memory and stores key */\n    entry = hi_malloc(sizeof(*entry));\n    if (entry == NULL)\n        return DICT_ERR;\n\n    entry->next = ht->table[index];\n    ht->table[index] = entry;\n\n    /* Set the hash entry fields. */\n    dictSetHashKey(ht, entry, key);\n    dictSetHashVal(ht, entry, val);\n    ht->used++;\n    return DICT_OK;\n}\n\n/* Add an element, discarding the old if the key already exists.\n * Return 1 if the key was added from scratch, 0 if there was already an\n * element with such key and dictReplace() just performed a value update\n * operation. */\nstatic int dictReplace(dict *ht, void *key, void *val) {\n    dictEntry *entry, auxentry;\n\n    /* Try to add the element. If the key\n     * does not exists dictAdd will succeed. */\n    if (dictAdd(ht, key, val) == DICT_OK)\n        return 1;\n    /* It already exists, get the entry */\n    entry = dictFind(ht, key);\n    if (entry == NULL)\n        return 0;\n\n    /* Free the old value and set the new one */\n    /* Set the new value and free the old one. Note that it is important\n     * to do that in this order, as the value may just be exactly the same\n     * as the previous one. In this context, think to reference counting,\n     * you want to increment (set), and then decrement (free), and not the\n     * reverse. */\n    auxentry = *entry;\n    dictSetHashVal(ht, entry, val);\n    dictFreeEntryVal(ht, &auxentry);\n    return 0;\n}\n\n/* Search and remove an element */\nstatic int dictDelete(dict *ht, const void *key) {\n    unsigned int h;\n    dictEntry *de, *prevde;\n\n    if (ht->size == 0)\n        return DICT_ERR;\n    h = dictHashKey(ht, key) & ht->sizemask;\n    de = ht->table[h];\n\n    prevde = NULL;\n    while(de) {\n        if (dictCompareHashKeys(ht,key,de->key)) {\n            /* Unlink the element from the list */\n            if (prevde)\n                prevde->next = de->next;\n            else\n                ht->table[h] = de->next;\n\n            dictFreeEntryKey(ht,de);\n            dictFreeEntryVal(ht,de);\n            hi_free(de);\n            ht->used--;\n            return DICT_OK;\n        }\n        prevde = de;\n        de = de->next;\n    }\n    return DICT_ERR; /* not found */\n}\n\n/* Destroy an entire hash table */\nstatic int _dictClear(dict *ht) {\n    unsigned long i;\n\n    /* Free all the elements */\n    for (i = 0; i < ht->size && ht->used > 0; i++) {\n        dictEntry *he, *nextHe;\n\n        if ((he = ht->table[i]) == NULL) continue;\n        while(he) {\n            nextHe = he->next;\n            dictFreeEntryKey(ht, he);\n            dictFreeEntryVal(ht, he);\n            hi_free(he);\n            ht->used--;\n            he = nextHe;\n        }\n    }\n    /* Free the table and the allocated cache structure */\n    hi_free(ht->table);\n    /* Re-initialize the table */\n    _dictReset(ht);\n    return DICT_OK; /* never fails */\n}\n\n/* Clear & Release the hash table */\nstatic void dictRelease(dict *ht) {\n    _dictClear(ht);\n    hi_free(ht);\n}\n\nstatic dictEntry *dictFind(dict *ht, const void *key) {\n    dictEntry *he;\n    unsigned int h;\n\n    if (ht->size == 0) return NULL;\n    h = dictHashKey(ht, key) & ht->sizemask;\n    he = ht->table[h];\n    while(he) {\n        if (dictCompareHashKeys(ht, key, he->key))\n            return he;\n        he = he->next;\n    }\n    return NULL;\n}\n\nstatic void dictInitIterator(dictIterator *iter, dict *ht) {\n    iter->ht = ht;\n    iter->index = -1;\n    iter->entry = NULL;\n    iter->nextEntry = NULL;\n}\n\nstatic dictEntry *dictNext(dictIterator *iter) {\n    while (1) {\n        if (iter->entry == NULL) {\n            iter->index++;\n            if (iter->index >=\n                    (signed)iter->ht->size) break;\n            iter->entry = iter->ht->table[iter->index];\n        } else {\n            iter->entry = iter->nextEntry;\n        }\n        if (iter->entry) {\n            /* We need to save the 'next' here, the iterator user\n             * may delete the entry we are returning. */\n            iter->nextEntry = iter->entry->next;\n            return iter->entry;\n        }\n    }\n    return NULL;\n}\n\n/* ------------------------- private functions ------------------------------ */\n\n/* Expand the hash table if needed */\nstatic int _dictExpandIfNeeded(dict *ht) {\n    /* If the hash table is empty expand it to the initial size,\n     * if the table is \"full\" double its size. */\n    if (ht->size == 0)\n        return dictExpand(ht, DICT_HT_INITIAL_SIZE);\n    if (ht->used == ht->size)\n        return dictExpand(ht, ht->size*2);\n    return DICT_OK;\n}\n\n/* Our hash table capability is a power of two */\nstatic unsigned long _dictNextPower(unsigned long size) {\n    unsigned long i = DICT_HT_INITIAL_SIZE;\n\n    if (size >= LONG_MAX) return LONG_MAX;\n    while(1) {\n        if (i >= size)\n            return i;\n        i *= 2;\n    }\n}\n\n/* Returns the index of a free slot that can be populated with\n * an hash entry for the given 'key'.\n * If the key already exists, -1 is returned. */\nstatic int _dictKeyIndex(dict *ht, const void *key) {\n    unsigned int h;\n    dictEntry *he;\n\n    /* Expand the hashtable if needed */\n    if (_dictExpandIfNeeded(ht) == DICT_ERR)\n        return -1;\n    /* Compute the key hash value */\n    h = dictHashKey(ht, key) & ht->sizemask;\n    /* Search if this slot does not already contain the given key */\n    he = ht->table[h];\n    while(he) {\n        if (dictCompareHashKeys(ht, key, he->key))\n            return -1;\n        he = he->next;\n    }\n    return h;\n}\n\n"
  },
  {
    "path": "dict.h",
    "content": "/* Hash table implementation.\n *\n * This file implements in memory hash tables with insert/del/replace/find/\n * get-random-element operations. Hash tables will auto resize if needed\n * tables of power of two in size are used, collisions are handled by\n * chaining. See the source code for more information... :)\n *\n * Copyright (c) 2006-2010, Salvatore Sanfilippo <antirez at gmail dot com>\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n *   * Redistributions of source code must retain the above copyright notice,\n *     this list of conditions and the following disclaimer.\n *   * Redistributions in binary form must reproduce the above copyright\n *     notice, this list of conditions and the following disclaimer in the\n *     documentation and/or other materials provided with the distribution.\n *   * Neither the name of Redis nor the names of its contributors may be used\n *     to endorse or promote products derived from this software without\n *     specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE\n * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n */\n\n#ifndef __DICT_H\n#define __DICT_H\n\n#define DICT_OK 0\n#define DICT_ERR 1\n\n/* Unused arguments generate annoying warnings... */\n#define DICT_NOTUSED(V) ((void) V)\n\ntypedef struct dictEntry {\n    void *key;\n    void *val;\n    struct dictEntry *next;\n} dictEntry;\n\ntypedef struct dictType {\n    unsigned int (*hashFunction)(const void *key);\n    void *(*keyDup)(void *privdata, const void *key);\n    void *(*valDup)(void *privdata, const void *obj);\n    int (*keyCompare)(void *privdata, const void *key1, const void *key2);\n    void (*keyDestructor)(void *privdata, void *key);\n    void (*valDestructor)(void *privdata, void *obj);\n} dictType;\n\ntypedef struct dict {\n    dictEntry **table;\n    dictType *type;\n    unsigned long size;\n    unsigned long sizemask;\n    unsigned long used;\n    void *privdata;\n} dict;\n\ntypedef struct dictIterator {\n    dict *ht;\n    int index;\n    dictEntry *entry, *nextEntry;\n} dictIterator;\n\n/* This is the initial size of every hash table */\n#define DICT_HT_INITIAL_SIZE     4\n\n/* ------------------------------- Macros ------------------------------------*/\n#define dictFreeEntryVal(ht, entry) \\\n    if ((ht)->type->valDestructor) \\\n        (ht)->type->valDestructor((ht)->privdata, (entry)->val)\n\n#define dictSetHashVal(ht, entry, _val_) do { \\\n    if ((ht)->type->valDup) \\\n        entry->val = (ht)->type->valDup((ht)->privdata, _val_); \\\n    else \\\n        entry->val = (_val_); \\\n} while(0)\n\n#define dictFreeEntryKey(ht, entry) \\\n    if ((ht)->type->keyDestructor) \\\n        (ht)->type->keyDestructor((ht)->privdata, (entry)->key)\n\n#define dictSetHashKey(ht, entry, _key_) do { \\\n    if ((ht)->type->keyDup) \\\n        entry->key = (ht)->type->keyDup((ht)->privdata, _key_); \\\n    else \\\n        entry->key = (_key_); \\\n} while(0)\n\n#define dictCompareHashKeys(ht, key1, key2) \\\n    (((ht)->type->keyCompare) ? \\\n        (ht)->type->keyCompare((ht)->privdata, key1, key2) : \\\n        (key1) == (key2))\n\n#define dictHashKey(ht, key) (ht)->type->hashFunction(key)\n\n#define dictGetEntryKey(he) ((he)->key)\n#define dictGetEntryVal(he) ((he)->val)\n#define dictSlots(ht) ((ht)->size)\n#define dictSize(ht) ((ht)->used)\n\n/* API */\nstatic unsigned int dictGenHashFunction(const unsigned char *buf, int len);\nstatic dict *dictCreate(dictType *type, void *privDataPtr);\nstatic int dictExpand(dict *ht, unsigned long size);\nstatic int dictAdd(dict *ht, void *key, void *val);\nstatic int dictReplace(dict *ht, void *key, void *val);\nstatic int dictDelete(dict *ht, const void *key);\nstatic void dictRelease(dict *ht);\nstatic dictEntry * dictFind(dict *ht, const void *key);\nstatic void dictInitIterator(dictIterator *iter, dict *ht);\nstatic dictEntry *dictNext(dictIterator *iter);\n\n#endif /* __DICT_H */\n"
  },
  {
    "path": "examples/CMakeLists.txt",
    "content": "INCLUDE(FindPkgConfig)\n# Check for GLib\n\nPKG_CHECK_MODULES(GLIB2 glib-2.0)\nif (GLIB2_FOUND)\n    INCLUDE_DIRECTORIES(${GLIB2_INCLUDE_DIRS})\n    LINK_DIRECTORIES(${GLIB2_LIBRARY_DIRS})\n    ADD_EXECUTABLE(example-glib example-glib.c)\n    TARGET_LINK_LIBRARIES(example-glib hiredis ${GLIB2_LIBRARIES})\nENDIF(GLIB2_FOUND)\n\nFIND_PATH(LIBEV ev.h\n    HINTS /usr/local /usr/opt/local\n    ENV LIBEV_INCLUDE_DIR)\n\nif (LIBEV)\n    # Just compile and link with libev\n    ADD_EXECUTABLE(example-libev example-libev.c)\n    TARGET_LINK_LIBRARIES(example-libev hiredis ev)\nENDIF()\n\nFIND_PATH(LIBEVENT event.h)\nif (LIBEVENT)\n    ADD_EXECUTABLE(example-libevent example-libevent.c)\n    TARGET_LINK_LIBRARIES(example-libevent hiredis event)\nENDIF()\n\nFIND_PATH(LIBHV hv/hv.h)\nIF (LIBHV)\n    ADD_EXECUTABLE(example-libhv example-libhv.c)\n    TARGET_LINK_LIBRARIES(example-libhv hiredis hv)\nENDIF()\n\nFIND_PATH(LIBUV uv.h)\nIF (LIBUV)\n    ADD_EXECUTABLE(example-libuv example-libuv.c)\n    TARGET_LINK_LIBRARIES(example-libuv hiredis uv)\nENDIF()\n\nFIND_PATH(LIBSDEVENT systemd/sd-event.h)\nIF (LIBSDEVENT)\n    ADD_EXECUTABLE(example-libsdevent example-libsdevent.c)\n    TARGET_LINK_LIBRARIES(example-libsdevent hiredis systemd)\nENDIF()\n\nIF (APPLE)\n    FIND_LIBRARY(CF CoreFoundation)\n    ADD_EXECUTABLE(example-macosx example-macosx.c)\n    TARGET_LINK_LIBRARIES(example-macosx hiredis ${CF})\nENDIF()\n\nIF (ENABLE_SSL)\n    ADD_EXECUTABLE(example-ssl example-ssl.c)\n    TARGET_LINK_LIBRARIES(example-ssl hiredis hiredis_ssl)\nENDIF()\n\nADD_EXECUTABLE(example example.c)\nTARGET_LINK_LIBRARIES(example hiredis)\n\nADD_EXECUTABLE(example-push example-push.c)\nTARGET_LINK_LIBRARIES(example-push hiredis)\n"
  },
  {
    "path": "examples/example-ae.c",
    "content": "#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <signal.h>\n\n#include <hiredis.h>\n#include <async.h>\n#include <adapters/ae.h>\n\n/* Put event loop in the global scope, so it can be explicitly stopped */\nstatic aeEventLoop *loop;\n\nvoid getCallback(redisAsyncContext *c, void *r, void *privdata) {\n    redisReply *reply = r;\n    if (reply == NULL) return;\n    printf(\"argv[%s]: %s\\n\", (char*)privdata, reply->str);\n\n    /* Disconnect after receiving the reply to GET */\n    redisAsyncDisconnect(c);\n}\n\nvoid connectCallback(const redisAsyncContext *c, int status) {\n    if (status != REDIS_OK) {\n        printf(\"Error: %s\\n\", c->errstr);\n        aeStop(loop);\n        return;\n    }\n\n    printf(\"Connected...\\n\");\n}\n\nvoid disconnectCallback(const redisAsyncContext *c, int status) {\n    if (status != REDIS_OK) {\n        printf(\"Error: %s\\n\", c->errstr);\n        aeStop(loop);\n        return;\n    }\n\n    printf(\"Disconnected...\\n\");\n    aeStop(loop);\n}\n\nint main (int argc, char **argv) {\n    signal(SIGPIPE, SIG_IGN);\n\n    redisAsyncContext *c = redisAsyncConnect(\"127.0.0.1\", 6379);\n    if (c->err) {\n        /* Let *c leak for now... */\n        printf(\"Error: %s\\n\", c->errstr);\n        return 1;\n    }\n\n    loop = aeCreateEventLoop(64);\n    redisAeAttach(loop, c);\n    redisAsyncSetConnectCallback(c,connectCallback);\n    redisAsyncSetDisconnectCallback(c,disconnectCallback);\n    redisAsyncCommand(c, NULL, NULL, \"SET key %b\", argv[argc-1], strlen(argv[argc-1]));\n    redisAsyncCommand(c, getCallback, (char*)\"end-1\", \"GET key\");\n    aeMain(loop);\n    return 0;\n}\n\n"
  },
  {
    "path": "examples/example-glib.c",
    "content": "#include <stdlib.h>\n\n#include <hiredis.h>\n#include <async.h>\n#include <adapters/glib.h>\n\nstatic GMainLoop *mainloop;\n\nstatic void\nconnect_cb (const redisAsyncContext *ac G_GNUC_UNUSED,\n            int status)\n{\n    if (status != REDIS_OK) {\n        g_printerr(\"Failed to connect: %s\\n\", ac->errstr);\n        g_main_loop_quit(mainloop);\n    } else {\n        g_printerr(\"Connected...\\n\");\n    }\n}\n\nstatic void\ndisconnect_cb (const redisAsyncContext *ac G_GNUC_UNUSED,\n               int status)\n{\n    if (status != REDIS_OK) {\n        g_error(\"Failed to disconnect: %s\", ac->errstr);\n    } else {\n        g_printerr(\"Disconnected...\\n\");\n        g_main_loop_quit(mainloop);\n    }\n}\n\nstatic void\ncommand_cb(redisAsyncContext *ac,\n           gpointer r,\n           gpointer user_data G_GNUC_UNUSED)\n{\n    redisReply *reply = r;\n\n    if (reply) {\n        g_print(\"REPLY: %s\\n\", reply->str);\n    }\n\n    redisAsyncDisconnect(ac);\n}\n\ngint\nmain (gint argc     G_GNUC_UNUSED,\n      gchar *argv[] G_GNUC_UNUSED)\n{\n    redisAsyncContext *ac;\n    GMainContext *context = NULL;\n    GSource *source;\n\n    ac = redisAsyncConnect(\"127.0.0.1\", 6379);\n    if (ac->err) {\n        g_printerr(\"%s\\n\", ac->errstr);\n        exit(EXIT_FAILURE);\n    }\n\n    source = redis_source_new(ac);\n    mainloop = g_main_loop_new(context, FALSE);\n    g_source_attach(source, context);\n\n    redisAsyncSetConnectCallback(ac, connect_cb);\n    redisAsyncSetDisconnectCallback(ac, disconnect_cb);\n    redisAsyncCommand(ac, command_cb, NULL, \"SET key 1234\");\n    redisAsyncCommand(ac, command_cb, NULL, \"GET key\");\n\n    g_main_loop_run(mainloop);\n\n    return EXIT_SUCCESS;\n}\n"
  },
  {
    "path": "examples/example-ivykis.c",
    "content": "#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <signal.h>\n\n#include <hiredis.h>\n#include <async.h>\n#include <adapters/ivykis.h>\n\nvoid getCallback(redisAsyncContext *c, void *r, void *privdata) {\n    redisReply *reply = r;\n    if (reply == NULL) return;\n    printf(\"argv[%s]: %s\\n\", (char*)privdata, reply->str);\n\n    /* Disconnect after receiving the reply to GET */\n    redisAsyncDisconnect(c);\n}\n\nvoid connectCallback(const redisAsyncContext *c, int status) {\n    if (status != REDIS_OK) {\n        printf(\"Error: %s\\n\", c->errstr);\n        return;\n    }\n    printf(\"Connected...\\n\");\n}\n\nvoid disconnectCallback(const redisAsyncContext *c, int status) {\n    if (status != REDIS_OK) {\n        printf(\"Error: %s\\n\", c->errstr);\n        return;\n    }\n    printf(\"Disconnected...\\n\");\n}\n\nint main (int argc, char **argv) {\n#ifndef _WIN32\n    signal(SIGPIPE, SIG_IGN);\n#endif\n\n    iv_init();\n\n    redisAsyncContext *c = redisAsyncConnect(\"127.0.0.1\", 6379);\n    if (c->err) {\n        /* Let *c leak for now... */\n        printf(\"Error: %s\\n\", c->errstr);\n        return 1;\n    }\n\n    redisIvykisAttach(c);\n    redisAsyncSetConnectCallback(c,connectCallback);\n    redisAsyncSetDisconnectCallback(c,disconnectCallback);\n    redisAsyncCommand(c, NULL, NULL, \"SET key %b\", argv[argc-1], strlen(argv[argc-1]));\n    redisAsyncCommand(c, getCallback, (char*)\"end-1\", \"GET key\");\n\n    iv_main();\n\n    iv_deinit();\n\n    return 0;\n}\n"
  },
  {
    "path": "examples/example-libev.c",
    "content": "#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <signal.h>\n\n#include <hiredis.h>\n#include <async.h>\n#include <adapters/libev.h>\n\nvoid getCallback(redisAsyncContext *c, void *r, void *privdata) {\n    redisReply *reply = r;\n    if (reply == NULL) return;\n    printf(\"argv[%s]: %s\\n\", (char*)privdata, reply->str);\n\n    /* Disconnect after receiving the reply to GET */\n    redisAsyncDisconnect(c);\n}\n\nvoid connectCallback(const redisAsyncContext *c, int status) {\n    if (status != REDIS_OK) {\n        printf(\"Error: %s\\n\", c->errstr);\n        return;\n    }\n    printf(\"Connected...\\n\");\n}\n\nvoid disconnectCallback(const redisAsyncContext *c, int status) {\n    if (status != REDIS_OK) {\n        printf(\"Error: %s\\n\", c->errstr);\n        return;\n    }\n    printf(\"Disconnected...\\n\");\n}\n\nint main (int argc, char **argv) {\n#ifndef _WIN32\n    signal(SIGPIPE, SIG_IGN);\n#endif\n\n    redisAsyncContext *c = redisAsyncConnect(\"127.0.0.1\", 6379);\n    if (c->err) {\n        /* Let *c leak for now... */\n        printf(\"Error: %s\\n\", c->errstr);\n        return 1;\n    }\n\n    redisLibevAttach(EV_DEFAULT_ c);\n    redisAsyncSetConnectCallback(c,connectCallback);\n    redisAsyncSetDisconnectCallback(c,disconnectCallback);\n    redisAsyncCommand(c, NULL, NULL, \"SET key %b\", argv[argc-1], strlen(argv[argc-1]));\n    redisAsyncCommand(c, getCallback, (char*)\"end-1\", \"GET key\");\n    ev_loop(EV_DEFAULT_ 0);\n    return 0;\n}\n"
  },
  {
    "path": "examples/example-libevent-ssl.c",
    "content": "#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <signal.h>\n\n#include <hiredis.h>\n#include <hiredis_ssl.h>\n#include <async.h>\n#include <adapters/libevent.h>\n\nvoid getCallback(redisAsyncContext *c, void *r, void *privdata) {\n    redisReply *reply = r;\n    if (reply == NULL) return;\n    printf(\"argv[%s]: %s\\n\", (char*)privdata, reply->str);\n\n    /* Disconnect after receiving the reply to GET */\n    redisAsyncDisconnect(c);\n}\n\nvoid connectCallback(const redisAsyncContext *c, int status) {\n    if (status != REDIS_OK) {\n        printf(\"Error: %s\\n\", c->errstr);\n        return;\n    }\n    printf(\"Connected...\\n\");\n}\n\nvoid disconnectCallback(const redisAsyncContext *c, int status) {\n    if (status != REDIS_OK) {\n        printf(\"Error: %s\\n\", c->errstr);\n        return;\n    }\n    printf(\"Disconnected...\\n\");\n}\n\nint main (int argc, char **argv) {\n#ifndef _WIN32\n    signal(SIGPIPE, SIG_IGN);\n#endif\n\n    struct event_base *base = event_base_new();\n    if (argc < 5) {\n        fprintf(stderr,\n                \"Usage: %s <key> <host> <port> <cert> <certKey> [ca]\\n\", argv[0]);\n        exit(1);\n    }\n\n    const char *value = argv[1];\n    size_t nvalue = strlen(value);\n\n    const char *hostname = argv[2];\n    int port = atoi(argv[3]);\n\n    const char *cert = argv[4];\n    const char *certKey = argv[5];\n    const char *caCert = argc > 5 ? argv[6] : NULL;\n\n    redisSSLContext *ssl;\n    redisSSLContextError ssl_error = REDIS_SSL_CTX_NONE;\n\n    redisInitOpenSSL();\n\n    ssl = redisCreateSSLContext(caCert, NULL,\n            cert, certKey, NULL, &ssl_error);\n    if (!ssl) {\n        printf(\"Error: %s\\n\", redisSSLContextGetError(ssl_error));\n        return 1;\n    }\n\n    redisAsyncContext *c = redisAsyncConnect(hostname, port);\n    if (c->err) {\n        /* Let *c leak for now... */\n        printf(\"Error: %s\\n\", c->errstr);\n        return 1;\n    }\n    if (redisInitiateSSLWithContext(&c->c, ssl) != REDIS_OK) {\n        printf(\"SSL Error!\\n\");\n        exit(1);\n    }\n\n    redisLibeventAttach(c,base);\n    redisAsyncSetConnectCallback(c,connectCallback);\n    redisAsyncSetDisconnectCallback(c,disconnectCallback);\n    redisAsyncCommand(c, NULL, NULL, \"SET key %b\", value, nvalue);\n    redisAsyncCommand(c, getCallback, (char*)\"end-1\", \"GET key\");\n    event_base_dispatch(base);\n\n    redisFreeSSLContext(ssl);\n    return 0;\n}\n"
  },
  {
    "path": "examples/example-libevent.c",
    "content": "#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <signal.h>\n\n#include <hiredis.h>\n#include <async.h>\n#include <adapters/libevent.h>\n\nvoid getCallback(redisAsyncContext *c, void *r, void *privdata) {\n    redisReply *reply = r;\n    if (reply == NULL) {\n        if (c->errstr) {\n            printf(\"errstr: %s\\n\", c->errstr);\n        }\n        return;\n    }\n    printf(\"argv[%s]: %s\\n\", (char*)privdata, reply->str);\n\n    /* Disconnect after receiving the reply to GET */\n    redisAsyncDisconnect(c);\n}\n\nvoid connectCallback(const redisAsyncContext *c, int status) {\n    if (status != REDIS_OK) {\n        printf(\"Error: %s\\n\", c->errstr);\n        return;\n    }\n    printf(\"Connected...\\n\");\n}\n\nvoid disconnectCallback(const redisAsyncContext *c, int status) {\n    if (status != REDIS_OK) {\n        printf(\"Error: %s\\n\", c->errstr);\n        return;\n    }\n    printf(\"Disconnected...\\n\");\n}\n\nint main (int argc, char **argv) {\n#ifndef _WIN32\n    signal(SIGPIPE, SIG_IGN);\n#endif\n\n    struct event_base *base = event_base_new();\n    redisOptions options = {0};\n    REDIS_OPTIONS_SET_TCP(&options, \"127.0.0.1\", 6379);\n    struct timeval tv = {0};\n    tv.tv_sec = 1;\n    options.connect_timeout = &tv;\n\n\n    redisAsyncContext *c = redisAsyncConnectWithOptions(&options);\n    if (c->err) {\n        /* Let *c leak for now... */\n        printf(\"Error: %s\\n\", c->errstr);\n        return 1;\n    }\n\n    redisLibeventAttach(c,base);\n    redisAsyncSetConnectCallback(c,connectCallback);\n    redisAsyncSetDisconnectCallback(c,disconnectCallback);\n    redisAsyncCommand(c, NULL, NULL, \"SET key %b\", argv[argc-1], strlen(argv[argc-1]));\n    redisAsyncCommand(c, getCallback, (char*)\"end-1\", \"GET key\");\n    event_base_dispatch(base);\n    return 0;\n}\n"
  },
  {
    "path": "examples/example-libhv.c",
    "content": "#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <signal.h>\n\n#include <hiredis.h>\n#include <async.h>\n#include <adapters/libhv.h>\n\nvoid getCallback(redisAsyncContext *c, void *r, void *privdata) {\n    redisReply *reply = r;\n    if (reply == NULL) return;\n    printf(\"argv[%s]: %s\\n\", (char*)privdata, reply->str);\n\n    /* Disconnect after receiving the reply to GET */\n    redisAsyncDisconnect(c);\n}\n\nvoid debugCallback(redisAsyncContext *c, void *r, void *privdata) {\n    (void)privdata;\n    redisReply *reply = r;\n\n    if (reply == NULL) {\n        printf(\"`DEBUG SLEEP` error: %s\\n\", c->errstr ? c->errstr : \"unknown error\");\n        return;\n    }\n\n    redisAsyncDisconnect(c);\n}\n\nvoid connectCallback(const redisAsyncContext *c, int status) {\n    if (status != REDIS_OK) {\n        printf(\"Error: %s\\n\", c->errstr);\n        return;\n    }\n    printf(\"Connected...\\n\");\n}\n\nvoid disconnectCallback(const redisAsyncContext *c, int status) {\n    if (status != REDIS_OK) {\n        printf(\"Error: %s\\n\", c->errstr);\n        return;\n    }\n    printf(\"Disconnected...\\n\");\n}\n\nint main (int argc, char **argv) {\n#ifndef _WIN32\n    signal(SIGPIPE, SIG_IGN);\n#endif\n\n    redisAsyncContext *c = redisAsyncConnect(\"127.0.0.1\", 6379);\n    if (c->err) {\n        /* Let *c leak for now... */\n        printf(\"Error: %s\\n\", c->errstr);\n        return 1;\n    }\n\n    hloop_t* loop = hloop_new(HLOOP_FLAG_QUIT_WHEN_NO_ACTIVE_EVENTS);\n    redisLibhvAttach(c, loop);\n    redisAsyncSetTimeout(c, (struct timeval){.tv_sec = 0, .tv_usec = 500000});\n    redisAsyncSetConnectCallback(c,connectCallback);\n    redisAsyncSetDisconnectCallback(c,disconnectCallback);\n    redisAsyncCommand(c, NULL, NULL, \"SET key %b\", argv[argc-1], strlen(argv[argc-1]));\n    redisAsyncCommand(c, getCallback, (char*)\"end-1\", \"GET key\");\n    redisAsyncCommand(c, debugCallback, NULL, \"DEBUG SLEEP %d\", 1);\n    hloop_run(loop);\n    hloop_free(&loop);\n    return 0;\n}\n"
  },
  {
    "path": "examples/example-libsdevent.c",
    "content": "#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <signal.h>\n\n#include <hiredis.h>\n#include <async.h>\n#include <adapters/libsdevent.h>\n\nvoid debugCallback(redisAsyncContext *c, void *r, void *privdata) {\n    (void)privdata;\n    redisReply *reply = r;\n    if (reply == NULL) {\n        /* The DEBUG SLEEP command will almost always fail, because we have set a 1 second timeout */\n        printf(\"`DEBUG SLEEP` error: %s\\n\", c->errstr ? c->errstr : \"unknown error\");\n        return;\n    }\n    /* Disconnect after receiving the reply of DEBUG SLEEP (which will not)*/\n    redisAsyncDisconnect(c);\n}\n\nvoid getCallback(redisAsyncContext *c, void *r, void *privdata) {\n    redisReply *reply = r;\n    if (reply == NULL) {\n        printf(\"`GET key` error: %s\\n\", c->errstr ? c->errstr : \"unknown error\");\n        return;\n    }\n    printf(\"`GET key` result: argv[%s]: %s\\n\", (char*)privdata, reply->str);\n\n    /* start another request that demonstrate timeout */\n    redisAsyncCommand(c, debugCallback, NULL, \"DEBUG SLEEP %f\", 1.5);\n}\n\nvoid connectCallback(const redisAsyncContext *c, int status) {\n    if (status != REDIS_OK) {\n        printf(\"connect error: %s\\n\", c->errstr);\n        return;\n    }\n    printf(\"Connected...\\n\");\n}\n\nvoid disconnectCallback(const redisAsyncContext *c, int status) {\n    if (status != REDIS_OK) {\n        printf(\"disconnect because of error: %s\\n\", c->errstr);\n        return;\n    }\n    printf(\"Disconnected...\\n\");\n}\n\nint main (int argc, char **argv) {\n    signal(SIGPIPE, SIG_IGN);\n\n    struct sd_event *event;\n    sd_event_default(&event);\n\n    redisAsyncContext *c = redisAsyncConnect(\"127.0.0.1\", 6379);\n    if (c->err) {\n        printf(\"Error: %s\\n\", c->errstr);\n        redisAsyncFree(c);\n        return 1;\n    }\n\n    redisLibsdeventAttach(c,event);\n    redisAsyncSetConnectCallback(c,connectCallback);\n    redisAsyncSetDisconnectCallback(c,disconnectCallback);\n    redisAsyncSetTimeout(c, (struct timeval){ .tv_sec = 1, .tv_usec = 0});\n\n    /*\n    In this demo, we first `set key`, then `get key` to demonstrate the basic usage of libsdevent adapter.\n    Then in `getCallback`, we start a `debug sleep` command to create 1.5 second long request.\n    Because we have set a 1 second timeout to the connection, the command will always fail with a\n    timeout error, which is shown in the `debugCallback`.\n    */\n\n    redisAsyncCommand(c, NULL, NULL, \"SET key %b\", argv[argc-1], strlen(argv[argc-1]));\n    redisAsyncCommand(c, getCallback, (char*)\"end-1\", \"GET key\");\n\n    /* sd-event does not quit when there are no handlers registered. Manually exit after 1.5 seconds */\n    sd_event_source *s;\n    sd_event_add_time_relative(event, &s, CLOCK_MONOTONIC, 1500000, 1, NULL, NULL);\n\n    sd_event_loop(event);\n    sd_event_source_disable_unref(s);\n    sd_event_unref(event);\n    return 0;\n}\n"
  },
  {
    "path": "examples/example-libuv.c",
    "content": "#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <signal.h>\n\n#include <hiredis.h>\n#include <async.h>\n#include <adapters/libuv.h>\n\nvoid debugCallback(redisAsyncContext *c, void *r, void *privdata) {\n    (void)privdata; //unused\n    redisReply *reply = r;\n    if (reply == NULL) {\n        /* The DEBUG SLEEP command will almost always fail, because we have set a 1 second timeout */\n        printf(\"`DEBUG SLEEP` error: %s\\n\", c->errstr ? c->errstr : \"unknown error\");\n        return;\n    }\n    /* Disconnect after receiving the reply of DEBUG SLEEP (which will not)*/\n    redisAsyncDisconnect(c);\n}\n\nvoid getCallback(redisAsyncContext *c, void *r, void *privdata) {\n    redisReply *reply = r;\n    if (reply == NULL) {\n        printf(\"`GET key` error: %s\\n\", c->errstr ? c->errstr : \"unknown error\");\n        return;\n    }\n    printf(\"`GET key` result: argv[%s]: %s\\n\", (char*)privdata, reply->str);\n\n    /* start another request that demonstrate timeout */\n    redisAsyncCommand(c, debugCallback, NULL, \"DEBUG SLEEP %f\", 1.5);\n}\n\nvoid connectCallback(const redisAsyncContext *c, int status) {\n    if (status != REDIS_OK) {\n        printf(\"connect error: %s\\n\", c->errstr);\n        return;\n    }\n    printf(\"Connected...\\n\");\n}\n\nvoid disconnectCallback(const redisAsyncContext *c, int status) {\n    if (status != REDIS_OK) {\n        printf(\"disconnect because of error: %s\\n\", c->errstr);\n        return;\n    }\n    printf(\"Disconnected...\\n\");\n}\n\nint main (int argc, char **argv) {\n#ifndef _WIN32\n    signal(SIGPIPE, SIG_IGN);\n#endif\n\n    uv_loop_t* loop = uv_default_loop();\n\n    redisAsyncContext *c = redisAsyncConnect(\"127.0.0.1\", 6379);\n    if (c->err) {\n        /* Let *c leak for now... */\n        printf(\"Error: %s\\n\", c->errstr);\n        return 1;\n    }\n\n    redisLibuvAttach(c,loop);\n    redisAsyncSetConnectCallback(c,connectCallback);\n    redisAsyncSetDisconnectCallback(c,disconnectCallback);\n    redisAsyncSetTimeout(c, (struct timeval){ .tv_sec = 1, .tv_usec = 0});\n\n    /*\n    In this demo, we first `set key`, then `get key` to demonstrate the basic usage of libuv adapter.\n    Then in `getCallback`, we start a `debug sleep` command to create 1.5 second long request.\n    Because we have set a 1 second timeout to the connection, the command will always fail with a\n    timeout error, which is shown in the `debugCallback`.\n    */\n\n    redisAsyncCommand(c, NULL, NULL, \"SET key %b\", argv[argc-1], strlen(argv[argc-1]));\n    redisAsyncCommand(c, getCallback, (char*)\"end-1\", \"GET key\");\n\n    uv_run(loop, UV_RUN_DEFAULT);\n    return 0;\n}\n"
  },
  {
    "path": "examples/example-macosx.c",
    "content": "/*\n * Copyright (c) 2015 Дмитрий Бахвалов (Dmitry Bakhvalov)\n *\n * Permission for license update:\n *   https://github.com/redis/hiredis/issues/1271#issuecomment-2258225227\n *\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n *   * Redistributions of source code must retain the above copyright notice,\n *     this list of conditions and the following disclaimer.\n *   * Redistributions in binary form must reproduce the above copyright\n *     notice, this list of conditions and the following disclaimer in the\n *     documentation and/or other materials provided with the distribution.\n *   * Neither the name of Redis nor the names of its contributors may be used\n *     to endorse or promote products derived from this software without\n *     specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE\n * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n */\n\n#include <stdio.h>\n\n#include <hiredis.h>\n#include <async.h>\n#include <adapters/macosx.h>\n\nvoid getCallback(redisAsyncContext *c, void *r, void *privdata) {\n    redisReply *reply = r;\n    if (reply == NULL) return;\n    printf(\"argv[%s]: %s\\n\", (char*)privdata, reply->str);\n\n    /* Disconnect after receiving the reply to GET */\n    redisAsyncDisconnect(c);\n}\n\nvoid connectCallback(const redisAsyncContext *c, int status) {\n    if (status != REDIS_OK) {\n        printf(\"Error: %s\\n\", c->errstr);\n        return;\n    }\n    printf(\"Connected...\\n\");\n}\n\nvoid disconnectCallback(const redisAsyncContext *c, int status) {\n    if (status != REDIS_OK) {\n        printf(\"Error: %s\\n\", c->errstr);\n        return;\n    }\n    CFRunLoopStop(CFRunLoopGetCurrent());\n    printf(\"Disconnected...\\n\");\n}\n\nint main (int argc, char **argv) {\n    signal(SIGPIPE, SIG_IGN);\n\n    CFRunLoopRef loop = CFRunLoopGetCurrent();\n    if( !loop ) {\n        printf(\"Error: Cannot get current run loop\\n\");\n        return 1;\n    }\n\n    redisAsyncContext *c = redisAsyncConnect(\"127.0.0.1\", 6379);\n    if (c->err) {\n        /* Let *c leak for now... */\n        printf(\"Error: %s\\n\", c->errstr);\n        return 1;\n    }\n\n    redisMacOSAttach(c, loop);\n\n    redisAsyncSetConnectCallback(c,connectCallback);\n    redisAsyncSetDisconnectCallback(c,disconnectCallback);\n\n    redisAsyncCommand(c, NULL, NULL, \"SET key %b\", argv[argc-1], strlen(argv[argc-1]));\n    redisAsyncCommand(c, getCallback, (char*)\"end-1\", \"GET key\");\n\n    CFRunLoopRun();\n\n    return 0;\n}\n\n"
  },
  {
    "path": "examples/example-poll.c",
    "content": "#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <signal.h>\n#include <unistd.h>\n\n#include <async.h>\n#include <adapters/poll.h>\n\n/* Put in the global scope, so that loop can be explicitly stopped */\nstatic int exit_loop = 0;\n\nvoid getCallback(redisAsyncContext *c, void *r, void *privdata) {\n    redisReply *reply = r;\n    if (reply == NULL) return;\n    printf(\"argv[%s]: %s\\n\", (char*)privdata, reply->str);\n\n    /* Disconnect after receiving the reply to GET */\n    redisAsyncDisconnect(c);\n}\n\nvoid connectCallback(const redisAsyncContext *c, int status) {\n    if (status != REDIS_OK) {\n        printf(\"Error: %s\\n\", c->errstr);\n        exit_loop = 1;\n        return;\n    }\n\n    printf(\"Connected...\\n\");\n}\n\nvoid disconnectCallback(const redisAsyncContext *c, int status) {\n    exit_loop = 1;\n    if (status != REDIS_OK) {\n        printf(\"Error: %s\\n\", c->errstr);\n        return;\n    }\n\n    printf(\"Disconnected...\\n\");\n}\n\nint main (int argc, char **argv) {\n    signal(SIGPIPE, SIG_IGN);\n\n    redisAsyncContext *c = redisAsyncConnect(\"127.0.0.1\", 6379);\n    if (c->err) {\n        /* Let *c leak for now... */\n        printf(\"Error: %s\\n\", c->errstr);\n        return 1;\n    }\n\n    redisPollAttach(c);\n    redisAsyncSetConnectCallback(c,connectCallback);\n    redisAsyncSetDisconnectCallback(c,disconnectCallback);\n    redisAsyncCommand(c, NULL, NULL, \"SET key %b\", argv[argc-1], strlen(argv[argc-1]));\n    redisAsyncCommand(c, getCallback, (char*)\"end-1\", \"GET key\");\n    while (!exit_loop)\n    {\n        redisPollTick(c, 0.1);\n    }\n    return 0;\n}\n"
  },
  {
    "path": "examples/example-push.c",
    "content": "/*\n * Copyright (c) 2020, Michael Grunder <michael dot grunder at gmail dot com>\n *\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n *   * Redistributions of source code must retain the above copyright notice,\n *     this list of conditions and the following disclaimer.\n *   * Redistributions in binary form must reproduce the above copyright\n *     notice, this list of conditions and the following disclaimer in the\n *     documentation and/or other materials provided with the distribution.\n *   * Neither the name of Redis nor the names of its contributors may be used\n *     to endorse or promote products derived from this software without\n *     specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE\n * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n */\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <hiredis.h>\n\n#define KEY_COUNT 5\n\n#define panicAbort(fmt, ...) \\\n    do { \\\n        fprintf(stderr, \"%s:%d:%s(): \" fmt, __FILE__, __LINE__, __func__, __VA_ARGS__); \\\n        exit(-1); \\\n    } while (0)\n\nstatic void assertReplyAndFree(redisContext *context, redisReply *reply, int type) {\n    if (reply == NULL)\n        panicAbort(\"NULL reply from server (error: %s)\", context->errstr);\n\n    if (reply->type != type) {\n        if (reply->type == REDIS_REPLY_ERROR)\n            fprintf(stderr, \"Redis Error: %s\\n\", reply->str);\n\n        panicAbort(\"Expected reply type %d but got type %d\", type, reply->type);\n    }\n\n    freeReplyObject(reply);\n}\n\n/* Switch to the RESP3 protocol and enable client tracking */\nstatic void enableClientTracking(redisContext *c) {\n    redisReply *reply = redisCommand(c, \"HELLO 3\");\n    if (reply == NULL || c->err) {\n        panicAbort(\"NULL reply or server error (error: %s)\", c->errstr);\n    }\n\n    if (reply->type != REDIS_REPLY_MAP) {\n        fprintf(stderr, \"Error: Can't send HELLO 3 command.  Are you sure you're \");\n        fprintf(stderr, \"connected to redis-server >= 6.0.0?\\nRedis error: %s\\n\",\n                        reply->type == REDIS_REPLY_ERROR ? reply->str : \"(unknown)\");\n        exit(-1);\n    }\n\n    freeReplyObject(reply);\n\n    /* Enable client tracking */\n    reply = redisCommand(c, \"CLIENT TRACKING ON\");\n    assertReplyAndFree(c, reply, REDIS_REPLY_STATUS);\n}\n\nvoid pushReplyHandler(void *privdata, void *r) {\n    redisReply *reply = r;\n    int *invalidations = privdata;\n\n    /* Sanity check on the invalidation reply */\n    if (reply->type != REDIS_REPLY_PUSH || reply->elements != 2 ||\n        reply->element[1]->type != REDIS_REPLY_ARRAY ||\n        reply->element[1]->element[0]->type != REDIS_REPLY_STRING)\n    {\n        panicAbort(\"%s\", \"Can't parse PUSH message!\");\n    }\n\n    /* Increment our invalidation count */\n    *invalidations += 1;\n\n    printf(\"pushReplyHandler(): INVALIDATE '%s' (invalidation count: %d)\\n\",\n           reply->element[1]->element[0]->str, *invalidations);\n\n    freeReplyObject(reply);\n}\n\n/* We aren't actually freeing anything here, but it is included to show that we can\n * have hiredis call our data destructor when freeing the context */\nvoid privdata_dtor(void *privdata) {\n    unsigned int *icount = privdata;\n    printf(\"privdata_dtor():  In context privdata dtor (invalidations: %u)\\n\", *icount);\n}\n\nint main(int argc, char **argv) {\n    unsigned int j, invalidations = 0;\n    redisContext *c;\n    redisReply *reply;\n\n    const char *hostname = (argc > 1) ? argv[1] : \"127.0.0.1\";\n    int port = (argc > 2) ? atoi(argv[2]) : 6379;\n\n    redisOptions o = {0};\n    REDIS_OPTIONS_SET_TCP(&o, hostname, port);\n\n    /* Set our context privdata to the address of our invalidation counter.  Each\n     * time our PUSH handler is called, hiredis will pass the privdata for context.\n     *\n     * This could also be done after we create the context like so:\n     *\n     *    c->privdata = &invalidations;\n     *    c->free_privdata = privdata_dtor;\n     */\n    REDIS_OPTIONS_SET_PRIVDATA(&o, &invalidations, privdata_dtor);\n\n    /* Set our custom PUSH message handler */\n    o.push_cb = pushReplyHandler;\n\n    c = redisConnectWithOptions(&o);\n    if (c == NULL || c->err)\n        panicAbort(\"Connection error:  %s\", c ? c->errstr : \"OOM\");\n\n    /* Enable RESP3 and turn on client tracking */\n    enableClientTracking(c);\n\n    /* Set some keys and then read them back.  Once we do that, Redis will deliver\n     * invalidation push messages whenever the key is modified */\n    for (j = 0; j < KEY_COUNT; j++) {\n        reply = redisCommand(c, \"SET key:%d initial:%d\", j, j);\n        assertReplyAndFree(c, reply, REDIS_REPLY_STATUS);\n\n        reply = redisCommand(c, \"GET key:%d\", j);\n        assertReplyAndFree(c, reply, REDIS_REPLY_STRING);\n    }\n\n    /* Trigger invalidation messages by updating keys we just read */\n    for (j = 0; j < KEY_COUNT; j++) {\n        printf(\"            main(): SET key:%d update:%d\\n\", j, j);\n        reply = redisCommand(c, \"SET key:%d update:%d\", j, j);\n        assertReplyAndFree(c, reply, REDIS_REPLY_STATUS);\n        printf(\"            main(): SET REPLY OK\\n\");\n    }\n\n    printf(\"\\nTotal detected invalidations: %d, expected: %d\\n\", invalidations, KEY_COUNT);\n\n    /* PING server */\n    redisFree(c);\n}\n"
  },
  {
    "path": "examples/example-qt.cpp",
    "content": "#include <iostream>\nusing namespace std;\n\n#include <QCoreApplication>\n#include <QTimer>\n\n#include \"example-qt.h\"\n\nvoid getCallback(redisAsyncContext *, void * r, void * privdata) {\n\n    redisReply * reply = static_cast<redisReply *>(r);\n    ExampleQt * ex = static_cast<ExampleQt *>(privdata);\n    if (reply == nullptr || ex == nullptr) return;\n\n    cout << \"key: \" << reply->str << endl;\n\n    ex->finish();\n}\n\nvoid ExampleQt::run() {\n\n    m_ctx = redisAsyncConnect(\"localhost\", 6379);\n\n    if (m_ctx->err) {\n        cerr << \"Error: \" << m_ctx->errstr << endl;\n        redisAsyncFree(m_ctx);\n        emit finished();\n    }\n\n    m_adapter.setContext(m_ctx);\n\n    redisAsyncCommand(m_ctx, NULL, NULL, \"SET key %s\", m_value);\n    redisAsyncCommand(m_ctx, getCallback, this, \"GET key\");\n}\n\nint main (int argc, char **argv) {\n\n    QCoreApplication app(argc, argv);\n\n    ExampleQt example(argv[argc-1]);\n\n    QObject::connect(&example, SIGNAL(finished()), &app, SLOT(quit()));\n    QTimer::singleShot(0, &example, SLOT(run()));\n\n    return app.exec();\n}\n"
  },
  {
    "path": "examples/example-qt.h",
    "content": "#ifndef __HIREDIS_EXAMPLE_QT_H\n#define __HIREDIS_EXAMPLE_QT_H\n\n#include <adapters/qt.h>\n\nclass ExampleQt : public QObject {\n\n    Q_OBJECT\n\n    public:\n        ExampleQt(const char * value, QObject * parent = 0)\n            : QObject(parent), m_value(value) {}\n\n    signals:\n        void finished();\n\n    public slots:\n        void run();\n\n    private:\n        void finish() { emit finished(); }\n\n    private:\n        const char * m_value;\n        redisAsyncContext * m_ctx;\n        RedisQtAdapter m_adapter;\n\n    friend\n    void getCallback(redisAsyncContext *, void *, void *);\n};\n\n#endif /* !__HIREDIS_EXAMPLE_QT_H */\n"
  },
  {
    "path": "examples/example-redismoduleapi.c",
    "content": "#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <signal.h>\n\n#include <hiredis.h>\n#include <async.h>\n#include <adapters/redismoduleapi.h>\n\nvoid debugCallback(redisAsyncContext *c, void *r, void *privdata) {\n    (void)privdata; //unused\n    redisReply *reply = r;\n    if (reply == NULL) {\n        /* The DEBUG SLEEP command will almost always fail, because we have set a 1 second timeout */\n        printf(\"`DEBUG SLEEP` error: %s\\n\", c->errstr ? c->errstr : \"unknown error\");\n        return;\n    }\n    /* Disconnect after receiving the reply of DEBUG SLEEP (which will not)*/\n    redisAsyncDisconnect(c);\n}\n\nvoid getCallback(redisAsyncContext *c, void *r, void *privdata) {\n    redisReply *reply = r;\n    if (reply == NULL) {\n        if (c->errstr) {\n            printf(\"errstr: %s\\n\", c->errstr);\n        }\n        return;\n    }\n    printf(\"argv[%s]: %s\\n\", (char*)privdata, reply->str);\n\n    /* start another request that demonstrate timeout */\n    redisAsyncCommand(c, debugCallback, NULL, \"DEBUG SLEEP %f\", 1.5);\n}\n\nvoid connectCallback(const redisAsyncContext *c, int status) {\n    if (status != REDIS_OK) {\n        printf(\"Error: %s\\n\", c->errstr);\n        return;\n    }\n    printf(\"Connected...\\n\");\n}\n\nvoid disconnectCallback(const redisAsyncContext *c, int status) {\n    if (status != REDIS_OK) {\n        printf(\"Error: %s\\n\", c->errstr);\n        return;\n    }\n    printf(\"Disconnected...\\n\");\n}\n\n/*\n * This example requires Redis 7.0 or above.\n *\n * 1- Compile this file as a shared library. Directory of \"redismodule.h\" must\n *    be in the include path.\n *       gcc -fPIC -shared -I../../redis/src/ -I.. example-redismoduleapi.c -o example-redismoduleapi.so\n *\n * 2- Load module:\n *       redis-server --loadmodule ./example-redismoduleapi.so value\n */\nint RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {\n\n    int ret = RedisModule_Init(ctx, \"example-redismoduleapi\", 1, REDISMODULE_APIVER_1);\n    if (ret != REDISMODULE_OK) {\n        printf(\"error module init \\n\");\n        return REDISMODULE_ERR;\n    }\n\n    if (redisModuleCompatibilityCheck() != REDIS_OK) {\n        printf(\"Redis 7.0 or above is required! \\n\");\n        return REDISMODULE_ERR;\n    }\n\n    redisAsyncContext *c = redisAsyncConnect(\"127.0.0.1\", 6379);\n    if (c->err) {\n        /* Let *c leak for now... */\n        printf(\"Error: %s\\n\", c->errstr);\n        return 1;\n    }\n\n    size_t len;\n    const char *val = RedisModule_StringPtrLen(argv[argc-1], &len);\n\n    RedisModuleCtx *module_ctx = RedisModule_GetDetachedThreadSafeContext(ctx);\n    redisModuleAttach(c, module_ctx);\n    redisAsyncSetConnectCallback(c,connectCallback);\n    redisAsyncSetDisconnectCallback(c,disconnectCallback);\n    redisAsyncSetTimeout(c, (struct timeval){ .tv_sec = 1, .tv_usec = 0});\n\n    /*\n    In this demo, we first `set key`, then `get key` to demonstrate the basic usage of the adapter.\n    Then in `getCallback`, we start a `debug sleep` command to create 1.5 second long request.\n    Because we have set a 1 second timeout to the connection, the command will always fail with a\n    timeout error, which is shown in the `debugCallback`.\n    */\n\n    redisAsyncCommand(c, NULL, NULL, \"SET key %b\", val, len);\n    redisAsyncCommand(c, getCallback, (char*)\"end-1\", \"GET key\");\n    return 0;\n}\n"
  },
  {
    "path": "examples/example-ssl.c",
    "content": "#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\n#include <hiredis.h>\n#include <hiredis_ssl.h>\n\n#ifdef _MSC_VER\n#include <winsock2.h> /* For struct timeval */\n#endif\n\nint main(int argc, char **argv) {\n    unsigned int j;\n    redisSSLContext *ssl;\n    redisSSLContextError ssl_error = REDIS_SSL_CTX_NONE;\n    redisContext *c;\n    redisReply *reply;\n    if (argc < 4) {\n        printf(\"Usage: %s <host> <port> <cert> <key> [ca]\\n\", argv[0]);\n        exit(1);\n    }\n    const char *hostname = (argc > 1) ? argv[1] : \"127.0.0.1\";\n    int port = atoi(argv[2]);\n    const char *cert = argv[3];\n    const char *key = argv[4];\n    const char *ca = argc > 4 ? argv[5] : NULL;\n\n    redisInitOpenSSL();\n    ssl = redisCreateSSLContext(ca, NULL, cert, key, NULL, &ssl_error);\n    if (!ssl || ssl_error != REDIS_SSL_CTX_NONE) {\n        printf(\"SSL Context error: %s\\n\", redisSSLContextGetError(ssl_error));\n        exit(1);\n    }\n\n    struct timeval tv = { 1, 500000 }; // 1.5 seconds\n    redisOptions options = {0};\n    REDIS_OPTIONS_SET_TCP(&options, hostname, port);\n    options.connect_timeout = &tv;\n    c = redisConnectWithOptions(&options);\n\n    if (c == NULL || c->err) {\n        if (c) {\n            printf(\"Connection error: %s\\n\", c->errstr);\n            redisFree(c);\n        } else {\n            printf(\"Connection error: can't allocate redis context\\n\");\n        }\n        exit(1);\n    }\n\n    if (redisInitiateSSLWithContext(c, ssl) != REDIS_OK) {\n        printf(\"Couldn't initialize SSL!\\n\");\n        printf(\"Error: %s\\n\", c->errstr);\n        redisFree(c);\n        exit(1);\n    }\n\n    /* PING server */\n    reply = redisCommand(c,\"PING\");\n    printf(\"PING: %s\\n\", reply->str);\n    freeReplyObject(reply);\n\n    /* Set a key */\n    reply = redisCommand(c,\"SET %s %s\", \"foo\", \"hello world\");\n    printf(\"SET: %s\\n\", reply->str);\n    freeReplyObject(reply);\n\n    /* Set a key using binary safe API */\n    reply = redisCommand(c,\"SET %b %b\", \"bar\", (size_t) 3, \"hello\", (size_t) 5);\n    printf(\"SET (binary API): %s\\n\", reply->str);\n    freeReplyObject(reply);\n\n    /* Try a GET and two INCR */\n    reply = redisCommand(c,\"GET foo\");\n    printf(\"GET foo: %s\\n\", reply->str);\n    freeReplyObject(reply);\n\n    reply = redisCommand(c,\"INCR counter\");\n    printf(\"INCR counter: %lld\\n\", reply->integer);\n    freeReplyObject(reply);\n    /* again ... */\n    reply = redisCommand(c,\"INCR counter\");\n    printf(\"INCR counter: %lld\\n\", reply->integer);\n    freeReplyObject(reply);\n\n    /* Create a list of numbers, from 0 to 9 */\n    reply = redisCommand(c,\"DEL mylist\");\n    freeReplyObject(reply);\n    for (j = 0; j < 10; j++) {\n        char buf[64];\n\n        snprintf(buf,64,\"%u\",j);\n        reply = redisCommand(c,\"LPUSH mylist element-%s\", buf);\n        freeReplyObject(reply);\n    }\n\n    /* Let's check what we have inside the list */\n    reply = redisCommand(c,\"LRANGE mylist 0 -1\");\n    if (reply->type == REDIS_REPLY_ARRAY) {\n        for (j = 0; j < reply->elements; j++) {\n            printf(\"%u) %s\\n\", j, reply->element[j]->str);\n        }\n    }\n    freeReplyObject(reply);\n\n    /* Disconnects and frees the context */\n    redisFree(c);\n\n    redisFreeSSLContext(ssl);\n\n    return 0;\n}\n"
  },
  {
    "path": "examples/example.c",
    "content": "#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <hiredis.h>\n\n#ifdef _MSC_VER\n#include <winsock2.h> /* For struct timeval */\n#endif\n\nstatic void example_argv_command(redisContext *c, size_t n) {\n    char **argv, tmp[42];\n    size_t *argvlen;\n    redisReply *reply;\n\n    /* We're allocating two additional elements for command and key */\n    argv = malloc(sizeof(*argv) * (2 + n));\n    argvlen = malloc(sizeof(*argvlen) * (2 + n));\n\n    /* First the command */\n    argv[0] = (char*)\"RPUSH\";\n    argvlen[0] = sizeof(\"RPUSH\") - 1;\n\n    /* Now our key */\n    argv[1] = (char*)\"argvlist\";\n    argvlen[1] = sizeof(\"argvlist\") - 1;\n\n    /* Now add the entries we wish to add to the list */\n    for (size_t i = 2; i < (n + 2); i++) {\n        argvlen[i] = snprintf(tmp, sizeof(tmp), \"argv-element-%zu\", i - 2);\n        argv[i] = strdup(tmp);\n    }\n\n    /* Execute the command using redisCommandArgv.  We're sending the arguments with\n     * two explicit arrays.  One for each argument's string, and the other for its\n     * length. */\n    reply = redisCommandArgv(c, n + 2, (const char **)argv, (const size_t*)argvlen);\n\n    if (reply == NULL || c->err) {\n        fprintf(stderr, \"Error:  Couldn't execute redisCommandArgv\\n\");\n        exit(1);\n    }\n\n    if (reply->type == REDIS_REPLY_INTEGER) {\n        printf(\"%s reply: %lld\\n\", argv[0], reply->integer);\n    }\n\n    freeReplyObject(reply);\n\n    /* Clean up */\n    for (size_t i = 2; i < (n + 2); i++) {\n        free(argv[i]);\n    }\n\n    free(argv);\n    free(argvlen);\n}\n\nint main(int argc, char **argv) {\n    unsigned int j, isunix = 0;\n    redisContext *c;\n    redisReply *reply;\n    const char *hostname = (argc > 1) ? argv[1] : \"127.0.0.1\";\n\n    if (argc > 2) {\n        if (*argv[2] == 'u' || *argv[2] == 'U') {\n            isunix = 1;\n            /* in this case, host is the path to the unix socket */\n            printf(\"Will connect to unix socket @%s\\n\", hostname);\n        }\n    }\n\n    int port = (argc > 2) ? atoi(argv[2]) : 6379;\n\n    struct timeval timeout = { 1, 500000 }; // 1.5 seconds\n    if (isunix) {\n        c = redisConnectUnixWithTimeout(hostname, timeout);\n    } else {\n        c = redisConnectWithTimeout(hostname, port, timeout);\n    }\n    if (c == NULL || c->err) {\n        if (c) {\n            printf(\"Connection error: %s\\n\", c->errstr);\n            redisFree(c);\n        } else {\n            printf(\"Connection error: can't allocate redis context\\n\");\n        }\n        exit(1);\n    }\n\n    /* PING server */\n    reply = redisCommand(c,\"PING\");\n    printf(\"PING: %s\\n\", reply->str);\n    freeReplyObject(reply);\n\n    /* Set a key */\n    reply = redisCommand(c,\"SET %s %s\", \"foo\", \"hello world\");\n    printf(\"SET: %s\\n\", reply->str);\n    freeReplyObject(reply);\n\n    /* Set a key using binary safe API */\n    reply = redisCommand(c,\"SET %b %b\", \"bar\", (size_t) 3, \"hello\", (size_t) 5);\n    printf(\"SET (binary API): %s\\n\", reply->str);\n    freeReplyObject(reply);\n\n    /* Try a GET and two INCR */\n    reply = redisCommand(c,\"GET foo\");\n    printf(\"GET foo: %s\\n\", reply->str);\n    freeReplyObject(reply);\n\n    reply = redisCommand(c,\"INCR counter\");\n    printf(\"INCR counter: %lld\\n\", reply->integer);\n    freeReplyObject(reply);\n    /* again ... */\n    reply = redisCommand(c,\"INCR counter\");\n    printf(\"INCR counter: %lld\\n\", reply->integer);\n    freeReplyObject(reply);\n\n    /* Create a list of numbers, from 0 to 9 */\n    reply = redisCommand(c,\"DEL mylist\");\n    freeReplyObject(reply);\n    for (j = 0; j < 10; j++) {\n        char buf[64];\n\n        snprintf(buf,64,\"%u\",j);\n        reply = redisCommand(c,\"LPUSH mylist element-%s\", buf);\n        freeReplyObject(reply);\n    }\n\n    /* Let's check what we have inside the list */\n    reply = redisCommand(c,\"LRANGE mylist 0 -1\");\n    if (reply->type == REDIS_REPLY_ARRAY) {\n        for (j = 0; j < reply->elements; j++) {\n            printf(\"%u) %s\\n\", j, reply->element[j]->str);\n        }\n    }\n    freeReplyObject(reply);\n\n    /* See function for an example of redisCommandArgv */\n    example_argv_command(c, 10);\n\n    /* Disconnects and frees the context */\n    redisFree(c);\n\n    return 0;\n}\n"
  },
  {
    "path": "fmacros.h",
    "content": "#ifndef __HIREDIS_FMACRO_H\n#define __HIREDIS_FMACRO_H\n\n#ifndef _AIX\n#define _XOPEN_SOURCE 600\n#define _POSIX_C_SOURCE 200112L\n#endif\n\n#if defined(__APPLE__) && defined(__MACH__)\n/* Enable TCP_KEEPALIVE */\n#define _DARWIN_C_SOURCE\n#endif\n\n#endif\n"
  },
  {
    "path": "hiredis-config.cmake.in",
    "content": "@PACKAGE_INIT@\n\nset_and_check(hiredis_INCLUDEDIR \"@PACKAGE_INCLUDE_INSTALL_DIR@\")\n\nIF (NOT TARGET hiredis::@hiredis_export_name@)\n\tINCLUDE(${CMAKE_CURRENT_LIST_DIR}/hiredis-targets.cmake)\nENDIF()\n\nSET(hiredis_LIBRARIES hiredis::@hiredis_export_name@)\nSET(hiredis_INCLUDE_DIRS ${hiredis_INCLUDEDIR})\n\ncheck_required_components(hiredis)\n\n"
  },
  {
    "path": "hiredis.c",
    "content": "/*\n * Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com>\n * Copyright (c) 2010-2014, Pieter Noordhuis <pcnoordhuis at gmail dot com>\n * Copyright (c) 2015, Matt Stancliff <matt at genges dot com>,\n *                     Jan-Erik Rediger <janerik at fnordig dot com>\n *\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n *   * Redistributions of source code must retain the above copyright notice,\n *     this list of conditions and the following disclaimer.\n *   * Redistributions in binary form must reproduce the above copyright\n *     notice, this list of conditions and the following disclaimer in the\n *     documentation and/or other materials provided with the distribution.\n *   * Neither the name of Redis nor the names of its contributors may be used\n *     to endorse or promote products derived from this software without\n *     specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE\n * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n */\n\n#include \"fmacros.h\"\n#include <string.h>\n#include <stdlib.h>\n#include <assert.h>\n#include <errno.h>\n#include <ctype.h>\n\n#include \"hiredis.h\"\n#include \"net.h\"\n#include \"sds.h\"\n#include \"async.h\"\n#include \"win32.h\"\n\nextern int redisContextUpdateConnectTimeout(redisContext *c, const struct timeval *timeout);\nextern int redisContextUpdateCommandTimeout(redisContext *c, const struct timeval *timeout);\n\nstatic redisContextFuncs redisContextDefaultFuncs = {\n    .close = redisNetClose,\n    .free_privctx = NULL,\n    .async_read = redisAsyncRead,\n    .async_write = redisAsyncWrite,\n    .read = redisNetRead,\n    .write = redisNetWrite\n};\n\nstatic redisReply *createReplyObject(int type);\nstatic void *createStringObject(const redisReadTask *task, char *str, size_t len);\nstatic void *createArrayObject(const redisReadTask *task, size_t elements);\nstatic void *createIntegerObject(const redisReadTask *task, long long value);\nstatic void *createDoubleObject(const redisReadTask *task, double value, char *str, size_t len);\nstatic void *createNilObject(const redisReadTask *task);\nstatic void *createBoolObject(const redisReadTask *task, int bval);\n\n/* Default set of functions to build the reply. Keep in mind that such a\n * function returning NULL is interpreted as OOM. */\nstatic redisReplyObjectFunctions defaultFunctions = {\n    createStringObject,\n    createArrayObject,\n    createIntegerObject,\n    createDoubleObject,\n    createNilObject,\n    createBoolObject,\n    freeReplyObject\n};\n\n/* Create a reply object */\nstatic redisReply *createReplyObject(int type) {\n    redisReply *r = hi_calloc(1,sizeof(*r));\n\n    if (r == NULL)\n        return NULL;\n\n    r->type = type;\n    return r;\n}\n\n/* Free a reply object */\nvoid freeReplyObject(void *reply) {\n    redisReply *r = reply;\n    size_t j;\n\n    if (r == NULL)\n        return;\n\n    switch(r->type) {\n    case REDIS_REPLY_INTEGER:\n    case REDIS_REPLY_NIL:\n    case REDIS_REPLY_BOOL:\n        break; /* Nothing to free */\n    case REDIS_REPLY_ARRAY:\n    case REDIS_REPLY_MAP:\n    case REDIS_REPLY_ATTR:\n    case REDIS_REPLY_SET:\n    case REDIS_REPLY_PUSH:\n        if (r->element != NULL) {\n            for (j = 0; j < r->elements; j++)\n                freeReplyObject(r->element[j]);\n            hi_free(r->element);\n        }\n        break;\n    case REDIS_REPLY_ERROR:\n    case REDIS_REPLY_STATUS:\n    case REDIS_REPLY_STRING:\n    case REDIS_REPLY_DOUBLE:\n    case REDIS_REPLY_VERB:\n    case REDIS_REPLY_BIGNUM:\n        hi_free(r->str);\n        break;\n    }\n    hi_free(r);\n}\n\nstatic void *createStringObject(const redisReadTask *task, char *str, size_t len) {\n    redisReply *r, *parent;\n    char *buf;\n\n    r = createReplyObject(task->type);\n    if (r == NULL)\n        return NULL;\n\n    assert(task->type == REDIS_REPLY_ERROR  ||\n           task->type == REDIS_REPLY_STATUS ||\n           task->type == REDIS_REPLY_STRING ||\n           task->type == REDIS_REPLY_VERB   ||\n           task->type == REDIS_REPLY_BIGNUM);\n\n    /* Copy string value */\n    if (task->type == REDIS_REPLY_VERB) {\n        buf = hi_malloc(len-4+1); /* Skip 4 bytes of verbatim type header. */\n        if (buf == NULL) goto oom;\n\n        memcpy(r->vtype,str,3);\n        r->vtype[3] = '\\0';\n        memcpy(buf,str+4,len-4);\n        buf[len-4] = '\\0';\n        r->len = len - 4;\n    } else {\n        buf = hi_malloc(len+1);\n        if (buf == NULL) goto oom;\n\n        memcpy(buf,str,len);\n        buf[len] = '\\0';\n        r->len = len;\n    }\n    r->str = buf;\n\n    if (task->parent) {\n        parent = task->parent->obj;\n        assert(parent->type == REDIS_REPLY_ARRAY ||\n               parent->type == REDIS_REPLY_MAP ||\n               parent->type == REDIS_REPLY_ATTR ||\n               parent->type == REDIS_REPLY_SET ||\n               parent->type == REDIS_REPLY_PUSH);\n        parent->element[task->idx] = r;\n    }\n    return r;\n\noom:\n    freeReplyObject(r);\n    return NULL;\n}\n\nstatic void *createArrayObject(const redisReadTask *task, size_t elements) {\n    redisReply *r, *parent;\n\n    r = createReplyObject(task->type);\n    if (r == NULL)\n        return NULL;\n\n    if (elements > 0) {\n        r->element = hi_calloc(elements,sizeof(redisReply*));\n        if (r->element == NULL) {\n            freeReplyObject(r);\n            return NULL;\n        }\n    }\n\n    r->elements = elements;\n\n    if (task->parent) {\n        parent = task->parent->obj;\n        assert(parent->type == REDIS_REPLY_ARRAY ||\n               parent->type == REDIS_REPLY_MAP ||\n               parent->type == REDIS_REPLY_ATTR ||\n               parent->type == REDIS_REPLY_SET ||\n               parent->type == REDIS_REPLY_PUSH);\n        parent->element[task->idx] = r;\n    }\n    return r;\n}\n\nstatic void *createIntegerObject(const redisReadTask *task, long long value) {\n    redisReply *r, *parent;\n\n    r = createReplyObject(REDIS_REPLY_INTEGER);\n    if (r == NULL)\n        return NULL;\n\n    r->integer = value;\n\n    if (task->parent) {\n        parent = task->parent->obj;\n        assert(parent->type == REDIS_REPLY_ARRAY ||\n               parent->type == REDIS_REPLY_MAP ||\n               parent->type == REDIS_REPLY_ATTR ||\n               parent->type == REDIS_REPLY_SET ||\n               parent->type == REDIS_REPLY_PUSH);\n        parent->element[task->idx] = r;\n    }\n    return r;\n}\n\nstatic void *createDoubleObject(const redisReadTask *task, double value, char *str, size_t len) {\n    redisReply *r, *parent;\n\n    if (len == SIZE_MAX) // Prevents hi_malloc(0) if len equals to SIZE_MAX\n        return NULL;\n\n    r = createReplyObject(REDIS_REPLY_DOUBLE);\n    if (r == NULL)\n        return NULL;\n\n    r->dval = value;\n    r->str = hi_malloc(len+1);\n    if (r->str == NULL) {\n        freeReplyObject(r);\n        return NULL;\n    }\n\n    /* The double reply also has the original protocol string representing a\n     * double as a null terminated string. This way the caller does not need\n     * to format back for string conversion, especially since Redis does efforts\n     * to make the string more human readable avoiding the calssical double\n     * decimal string conversion artifacts. */\n    memcpy(r->str, str, len);\n    r->str[len] = '\\0';\n    r->len = len;\n\n    if (task->parent) {\n        parent = task->parent->obj;\n        assert(parent->type == REDIS_REPLY_ARRAY ||\n               parent->type == REDIS_REPLY_MAP ||\n               parent->type == REDIS_REPLY_ATTR ||\n               parent->type == REDIS_REPLY_SET ||\n               parent->type == REDIS_REPLY_PUSH);\n        parent->element[task->idx] = r;\n    }\n    return r;\n}\n\nstatic void *createNilObject(const redisReadTask *task) {\n    redisReply *r, *parent;\n\n    r = createReplyObject(REDIS_REPLY_NIL);\n    if (r == NULL)\n        return NULL;\n\n    if (task->parent) {\n        parent = task->parent->obj;\n        assert(parent->type == REDIS_REPLY_ARRAY ||\n               parent->type == REDIS_REPLY_MAP ||\n               parent->type == REDIS_REPLY_ATTR ||\n               parent->type == REDIS_REPLY_SET ||\n               parent->type == REDIS_REPLY_PUSH);\n        parent->element[task->idx] = r;\n    }\n    return r;\n}\n\nstatic void *createBoolObject(const redisReadTask *task, int bval) {\n    redisReply *r, *parent;\n\n    r = createReplyObject(REDIS_REPLY_BOOL);\n    if (r == NULL)\n        return NULL;\n\n    r->integer = bval != 0;\n\n    if (task->parent) {\n        parent = task->parent->obj;\n        assert(parent->type == REDIS_REPLY_ARRAY ||\n               parent->type == REDIS_REPLY_MAP ||\n               parent->type == REDIS_REPLY_ATTR ||\n               parent->type == REDIS_REPLY_SET ||\n               parent->type == REDIS_REPLY_PUSH);\n        parent->element[task->idx] = r;\n    }\n    return r;\n}\n\n/* Return the number of digits of 'v' when converted to string in radix 10.\n * Implementation borrowed from link in redis/src/util.c:string2ll(). */\nstatic uint32_t countDigits(uint64_t v) {\n  uint32_t result = 1;\n  for (;;) {\n    if (v < 10) return result;\n    if (v < 100) return result + 1;\n    if (v < 1000) return result + 2;\n    if (v < 10000) return result + 3;\n    v /= 10000U;\n    result += 4;\n  }\n}\n\n/* Helper that calculates the bulk length given a certain string length. */\nstatic size_t bulklen(size_t len) {\n    return 1+countDigits(len)+2+len+2;\n}\n\nint redisvFormatCommand(char **target, const char *format, va_list ap) {\n    const char *c = format;\n    char *cmd = NULL; /* final command */\n    int pos; /* position in final command */\n    sds curarg, newarg; /* current argument */\n    int touched = 0; /* was the current argument touched? */\n    char **curargv = NULL, **newargv = NULL;\n    int argc = 0;\n    int totlen = 0;\n    int error_type = 0; /* 0 = no error; -1 = memory error; -2 = format error */\n    int j;\n\n    /* Abort if there is not target to set */\n    if (target == NULL)\n        return -1;\n\n    /* Build the command string accordingly to protocol */\n    curarg = sdsempty();\n    if (curarg == NULL)\n        return -1;\n\n    while(*c != '\\0') {\n        if (*c != '%' || c[1] == '\\0') {\n            if (*c == ' ') {\n                if (touched) {\n                    newargv = hi_realloc(curargv,sizeof(char*)*(argc+1));\n                    if (newargv == NULL) goto memory_err;\n                    curargv = newargv;\n                    curargv[argc++] = curarg;\n                    totlen += bulklen(sdslen(curarg));\n\n                    /* curarg is put in argv so it can be overwritten. */\n                    curarg = sdsempty();\n                    if (curarg == NULL) goto memory_err;\n                    touched = 0;\n                }\n            } else {\n                newarg = sdscatlen(curarg,c,1);\n                if (newarg == NULL) goto memory_err;\n                curarg = newarg;\n                touched = 1;\n            }\n        } else {\n            char *arg;\n            size_t size;\n\n            /* Set newarg so it can be checked even if it is not touched. */\n            newarg = curarg;\n\n            switch(c[1]) {\n            case 's':\n                arg = va_arg(ap,char*);\n                size = strlen(arg);\n                if (size > 0)\n                    newarg = sdscatlen(curarg,arg,size);\n                break;\n            case 'b':\n                arg = va_arg(ap,char*);\n                size = va_arg(ap,size_t);\n                if (size > 0)\n                    newarg = sdscatlen(curarg,arg,size);\n                break;\n            case '%':\n                newarg = sdscat(curarg,\"%\");\n                break;\n            default:\n                /* Try to detect printf format */\n                {\n                    static const char intfmts[] = \"diouxX\";\n                    static const char flags[] = \"#0-+ \";\n                    char _format[16];\n                    const char *_p = c+1;\n                    size_t _l = 0;\n                    va_list _cpy;\n\n                    /* Flags */\n                    while (*_p != '\\0' && strchr(flags,*_p) != NULL) _p++;\n\n                    /* Field width */\n                    while (*_p != '\\0' && isdigit((int) *_p)) _p++;\n\n                    /* Precision */\n                    if (*_p == '.') {\n                        _p++;\n                        while (*_p != '\\0' && isdigit((int) *_p)) _p++;\n                    }\n\n                    /* Copy va_list before consuming with va_arg */\n                    va_copy(_cpy,ap);\n\n                    /* Make sure we have more characters otherwise strchr() accepts\n                     * '\\0' as an integer specifier. This is checked after above\n                     * va_copy() to avoid UB in fmt_invalid's call to va_end(). */\n                    if (*_p == '\\0') goto fmt_invalid;\n\n                    /* Integer conversion (without modifiers) */\n                    if (strchr(intfmts,*_p) != NULL) {\n                        va_arg(ap,int);\n                        goto fmt_valid;\n                    }\n\n                    /* Double conversion (without modifiers) */\n                    if (strchr(\"eEfFgGaA\",*_p) != NULL) {\n                        va_arg(ap,double);\n                        goto fmt_valid;\n                    }\n\n                    /* Size: char */\n                    if (_p[0] == 'h' && _p[1] == 'h') {\n                        _p += 2;\n                        if (*_p != '\\0' && strchr(intfmts,*_p) != NULL) {\n                            va_arg(ap,int); /* char gets promoted to int */\n                            goto fmt_valid;\n                        }\n                        goto fmt_invalid;\n                    }\n\n                    /* Size: short */\n                    if (_p[0] == 'h') {\n                        _p += 1;\n                        if (*_p != '\\0' && strchr(intfmts,*_p) != NULL) {\n                            va_arg(ap,int); /* short gets promoted to int */\n                            goto fmt_valid;\n                        }\n                        goto fmt_invalid;\n                    }\n\n                    /* Size: long long */\n                    if (_p[0] == 'l' && _p[1] == 'l') {\n                        _p += 2;\n                        if (*_p != '\\0' && strchr(intfmts,*_p) != NULL) {\n                            va_arg(ap,long long);\n                            goto fmt_valid;\n                        }\n                        goto fmt_invalid;\n                    }\n\n                    /* Size: long */\n                    if (_p[0] == 'l') {\n                        _p += 1;\n                        if (*_p != '\\0' && strchr(intfmts,*_p) != NULL) {\n                            va_arg(ap,long);\n                            goto fmt_valid;\n                        }\n                        goto fmt_invalid;\n                    }\n\n                fmt_invalid:\n                    va_end(_cpy);\n                    goto format_err;\n\n                fmt_valid:\n                    _l = (_p+1)-c;\n                    if (_l < sizeof(_format)-2) {\n                        memcpy(_format,c,_l);\n                        _format[_l] = '\\0';\n                        newarg = sdscatvprintf(curarg,_format,_cpy);\n\n                        /* Update current position (note: outer blocks\n                         * increment c twice so compensate here) */\n                        c = _p-1;\n                    }\n\n                    va_end(_cpy);\n                    break;\n                }\n            }\n\n            if (newarg == NULL) goto memory_err;\n            curarg = newarg;\n\n            touched = 1;\n            c++;\n            if (*c == '\\0')\n                break;\n        }\n        c++;\n    }\n\n    /* Add the last argument if needed */\n    if (touched) {\n        newargv = hi_realloc(curargv,sizeof(char*)*(argc+1));\n        if (newargv == NULL) goto memory_err;\n        curargv = newargv;\n        curargv[argc++] = curarg;\n        totlen += bulklen(sdslen(curarg));\n    } else {\n        sdsfree(curarg);\n    }\n\n    /* Clear curarg because it was put in curargv or was free'd. */\n    curarg = NULL;\n\n    /* Add bytes needed to hold multi bulk count */\n    totlen += 1+countDigits(argc)+2;\n\n    /* Build the command at protocol level */\n    cmd = hi_malloc(totlen+1);\n    if (cmd == NULL) goto memory_err;\n\n    pos = sprintf(cmd,\"*%d\\r\\n\",argc);\n    for (j = 0; j < argc; j++) {\n        pos += sprintf(cmd+pos,\"$%zu\\r\\n\",sdslen(curargv[j]));\n        memcpy(cmd+pos,curargv[j],sdslen(curargv[j]));\n        pos += sdslen(curargv[j]);\n        sdsfree(curargv[j]);\n        cmd[pos++] = '\\r';\n        cmd[pos++] = '\\n';\n    }\n    assert(pos == totlen);\n    cmd[pos] = '\\0';\n\n    hi_free(curargv);\n    *target = cmd;\n    return totlen;\n\nformat_err:\n    error_type = -2;\n    goto cleanup;\n\nmemory_err:\n    error_type = -1;\n    goto cleanup;\n\ncleanup:\n    if (curargv) {\n        while(argc--)\n            sdsfree(curargv[argc]);\n        hi_free(curargv);\n    }\n\n    sdsfree(curarg);\n    hi_free(cmd);\n\n    return error_type;\n}\n\n/* Format a command according to the Redis protocol. This function\n * takes a format similar to printf:\n *\n * %s represents a C null terminated string you want to interpolate\n * %b represents a binary safe string\n *\n * When using %b you need to provide both the pointer to the string\n * and the length in bytes as a size_t. Examples:\n *\n * len = redisFormatCommand(target, \"GET %s\", mykey);\n * len = redisFormatCommand(target, \"SET %s %b\", mykey, myval, myvallen);\n */\nint redisFormatCommand(char **target, const char *format, ...) {\n    va_list ap;\n    int len;\n    va_start(ap,format);\n    len = redisvFormatCommand(target,format,ap);\n    va_end(ap);\n\n    /* The API says \"-1\" means bad result, but we now also return \"-2\" in some\n     * cases.  Force the return value to always be -1. */\n    if (len < 0)\n        len = -1;\n\n    return len;\n}\n\n/* Format a command according to the Redis protocol using an sds string and\n * sdscatfmt for the processing of arguments. This function takes the\n * number of arguments, an array with arguments and an array with their\n * lengths. If the latter is set to NULL, strlen will be used to compute the\n * argument lengths.\n */\nlong long redisFormatSdsCommandArgv(sds *target, int argc, const char **argv,\n                                    const size_t *argvlen)\n{\n    sds cmd, aux;\n    unsigned long long totlen, len;\n    int j;\n\n    /* Abort on a NULL target */\n    if (target == NULL)\n        return -1;\n\n    /* Calculate our total size */\n    totlen = 1+countDigits(argc)+2;\n    for (j = 0; j < argc; j++) {\n        len = argvlen ? argvlen[j] : strlen(argv[j]);\n        totlen += bulklen(len);\n    }\n\n    /* Use an SDS string for command construction */\n    cmd = sdsempty();\n    if (cmd == NULL)\n        return -1;\n\n    /* We already know how much storage we need */\n    aux = sdsMakeRoomFor(cmd, totlen);\n    if (aux == NULL) {\n        sdsfree(cmd);\n        return -1;\n    }\n\n    cmd = aux;\n\n    /* Construct command */\n    cmd = sdscatfmt(cmd, \"*%i\\r\\n\", argc);\n    for (j=0; j < argc; j++) {\n        len = argvlen ? argvlen[j] : strlen(argv[j]);\n        cmd = sdscatfmt(cmd, \"$%U\\r\\n\", len);\n        cmd = sdscatlen(cmd, argv[j], len);\n        cmd = sdscatlen(cmd, \"\\r\\n\", sizeof(\"\\r\\n\")-1);\n    }\n\n    assert(sdslen(cmd)==totlen);\n\n    *target = cmd;\n    return totlen;\n}\n\nvoid redisFreeSdsCommand(sds cmd) {\n    sdsfree(cmd);\n}\n\n/* Format a command according to the Redis protocol. This function takes the\n * number of arguments, an array with arguments and an array with their\n * lengths. If the latter is set to NULL, strlen will be used to compute the\n * argument lengths.\n */\nlong long redisFormatCommandArgv(char **target, int argc, const char **argv, const size_t *argvlen) {\n    char *cmd = NULL; /* final command */\n    size_t pos; /* position in final command */\n    size_t len, totlen;\n    int j;\n\n    /* Abort on a NULL target */\n    if (target == NULL)\n        return -1;\n\n    /* Calculate number of bytes needed for the command */\n    totlen = 1+countDigits(argc)+2;\n    for (j = 0; j < argc; j++) {\n        len = argvlen ? argvlen[j] : strlen(argv[j]);\n        totlen += bulklen(len);\n    }\n\n    /* Build the command at protocol level */\n    cmd = hi_malloc(totlen+1);\n    if (cmd == NULL)\n        return -1;\n\n    pos = sprintf(cmd,\"*%d\\r\\n\",argc);\n    for (j = 0; j < argc; j++) {\n        len = argvlen ? argvlen[j] : strlen(argv[j]);\n        pos += sprintf(cmd+pos,\"$%zu\\r\\n\",len);\n        memcpy(cmd+pos,argv[j],len);\n        pos += len;\n        cmd[pos++] = '\\r';\n        cmd[pos++] = '\\n';\n    }\n    assert(pos == totlen);\n    cmd[pos] = '\\0';\n\n    *target = cmd;\n    return totlen;\n}\n\nvoid redisFreeCommand(char *cmd) {\n    hi_free(cmd);\n}\n\nvoid __redisSetError(redisContext *c, int type, const char *str) {\n    size_t len;\n\n    c->err = type;\n    if (str != NULL) {\n        len = strlen(str);\n        len = len < (sizeof(c->errstr)-1) ? len : (sizeof(c->errstr)-1);\n        memcpy(c->errstr,str,len);\n        c->errstr[len] = '\\0';\n    } else {\n        /* Only REDIS_ERR_IO may lack a description! */\n        assert(type == REDIS_ERR_IO);\n        strerror_r(errno, c->errstr, sizeof(c->errstr));\n    }\n}\n\nredisReader *redisReaderCreate(void) {\n    return redisReaderCreateWithFunctions(&defaultFunctions);\n}\n\nstatic void redisPushAutoFree(void *privdata, void *reply) {\n    (void)privdata;\n    freeReplyObject(reply);\n}\n\nstatic redisContext *redisContextInit(void) {\n    redisContext *c;\n\n    c = hi_calloc(1, sizeof(*c));\n    if (c == NULL)\n        return NULL;\n\n    c->funcs = &redisContextDefaultFuncs;\n\n    c->obuf = sdsempty();\n    c->reader = redisReaderCreate();\n    c->fd = REDIS_INVALID_FD;\n\n    if (c->obuf == NULL || c->reader == NULL) {\n        redisFree(c);\n        return NULL;\n    }\n\n    return c;\n}\n\nvoid redisFree(redisContext *c) {\n    if (c == NULL)\n        return;\n\n    if (c->funcs && c->funcs->close) {\n        c->funcs->close(c);\n    }\n\n    sdsfree(c->obuf);\n    redisReaderFree(c->reader);\n    hi_free(c->tcp.host);\n    hi_free(c->tcp.source_addr);\n    hi_free(c->unix_sock.path);\n    hi_free(c->connect_timeout);\n    hi_free(c->command_timeout);\n    hi_free(c->saddr);\n\n    if (c->privdata && c->free_privdata)\n        c->free_privdata(c->privdata);\n\n    if (c->funcs && c->funcs->free_privctx)\n        c->funcs->free_privctx(c->privctx);\n\n    memset(c, 0xff, sizeof(*c));\n    hi_free(c);\n}\n\nredisFD redisFreeKeepFd(redisContext *c) {\n    redisFD fd = c->fd;\n    c->fd = REDIS_INVALID_FD;\n    redisFree(c);\n    return fd;\n}\n\nint redisReconnect(redisContext *c) {\n    c->err = 0;\n    memset(c->errstr, '\\0', strlen(c->errstr));\n\n    if (c->privctx && c->funcs->free_privctx) {\n        c->funcs->free_privctx(c->privctx);\n        c->privctx = NULL;\n    }\n\n    if (c->funcs && c->funcs->close) {\n        c->funcs->close(c);\n    }\n\n    sdsfree(c->obuf);\n    redisReaderFree(c->reader);\n\n    c->obuf = sdsempty();\n    c->reader = redisReaderCreate();\n\n    if (c->obuf == NULL || c->reader == NULL) {\n        __redisSetError(c, REDIS_ERR_OOM, \"Out of memory\");\n        return REDIS_ERR;\n    }\n\n    int ret = REDIS_ERR;\n    if (c->connection_type == REDIS_CONN_TCP) {\n        ret = redisContextConnectBindTcp(c, c->tcp.host, c->tcp.port,\n               c->connect_timeout, c->tcp.source_addr);\n    } else if (c->connection_type == REDIS_CONN_UNIX) {\n        ret = redisContextConnectUnix(c, c->unix_sock.path, c->connect_timeout);\n    } else {\n        /* Something bad happened here and shouldn't have. There isn't\n           enough information in the context to reconnect. */\n        __redisSetError(c,REDIS_ERR_OTHER,\"Not enough information to reconnect\");\n        ret = REDIS_ERR;\n    }\n\n    if (c->command_timeout != NULL && (c->flags & REDIS_BLOCK) && c->fd != REDIS_INVALID_FD) {\n        redisContextSetTimeout(c, *c->command_timeout);\n    }\n\n    return ret;\n}\n\nredisContext *redisConnectWithOptions(const redisOptions *options) {\n    redisContext *c = redisContextInit();\n    if (c == NULL) {\n        return NULL;\n    }\n    if (!(options->options & REDIS_OPT_NONBLOCK)) {\n        c->flags |= REDIS_BLOCK;\n    }\n    if (options->options & REDIS_OPT_REUSEADDR) {\n        c->flags |= REDIS_REUSEADDR;\n    }\n    if (options->options & REDIS_OPT_NOAUTOFREE) {\n        c->flags |= REDIS_NO_AUTO_FREE;\n    }\n    if (options->options & REDIS_OPT_NOAUTOFREEREPLIES) {\n        c->flags |= REDIS_NO_AUTO_FREE_REPLIES;\n    }\n    if (options->options & REDIS_OPT_PREFER_IPV4) {\n        c->flags |= REDIS_PREFER_IPV4;\n    }\n    if (options->options & REDIS_OPT_PREFER_IPV6) {\n        c->flags |= REDIS_PREFER_IPV6;\n    }\n    if (options->options & REDIS_OPT_SET_SOCK_CLOEXEC) {\n        c->flags |= REDIS_OPT_SET_SOCK_CLOEXEC;\n    }\n\n    /* Set any user supplied RESP3 PUSH handler or use freeReplyObject\n     * as a default unless specifically flagged that we don't want one. */\n    if (options->push_cb != NULL)\n        redisSetPushCallback(c, options->push_cb);\n    else if (!(options->options & REDIS_OPT_NO_PUSH_AUTOFREE))\n        redisSetPushCallback(c, redisPushAutoFree);\n\n    c->privdata = options->privdata;\n    c->free_privdata = options->free_privdata;\n\n    if (redisContextUpdateConnectTimeout(c, options->connect_timeout) != REDIS_OK ||\n        redisContextUpdateCommandTimeout(c, options->command_timeout) != REDIS_OK) {\n        __redisSetError(c, REDIS_ERR_OOM, \"Out of memory\");\n        return c;\n    }\n\n    if (options->type == REDIS_CONN_TCP) {\n        redisContextConnectBindTcp(c, options->endpoint.tcp.ip,\n                                   options->endpoint.tcp.port, options->connect_timeout,\n                                   options->endpoint.tcp.source_addr);\n    } else if (options->type == REDIS_CONN_UNIX) {\n        redisContextConnectUnix(c, options->endpoint.unix_socket,\n                                options->connect_timeout);\n    } else if (options->type == REDIS_CONN_USERFD) {\n        c->fd = options->endpoint.fd;\n        c->flags |= REDIS_CONNECTED;\n    } else {\n        redisFree(c);\n        return NULL;\n    }\n\n    if (c->err == 0 && c->fd != REDIS_INVALID_FD &&\n        options->command_timeout != NULL && (c->flags & REDIS_BLOCK))\n    {\n        redisContextSetTimeout(c, *options->command_timeout);\n    }\n\n    return c;\n}\n\n/* Connect to a Redis instance. On error the field error in the returned\n * context will be set to the return value of the error function.\n * When no set of reply functions is given, the default set will be used. */\nredisContext *redisConnect(const char *ip, int port) {\n    redisOptions options = {0};\n    REDIS_OPTIONS_SET_TCP(&options, ip, port);\n    return redisConnectWithOptions(&options);\n}\n\nredisContext *redisConnectWithTimeout(const char *ip, int port, const struct timeval tv) {\n    redisOptions options = {0};\n    REDIS_OPTIONS_SET_TCP(&options, ip, port);\n    options.connect_timeout = &tv;\n    return redisConnectWithOptions(&options);\n}\n\nredisContext *redisConnectNonBlock(const char *ip, int port) {\n    redisOptions options = {0};\n    REDIS_OPTIONS_SET_TCP(&options, ip, port);\n    options.options |= REDIS_OPT_NONBLOCK;\n    return redisConnectWithOptions(&options);\n}\n\nredisContext *redisConnectBindNonBlock(const char *ip, int port,\n                                       const char *source_addr) {\n    redisOptions options = {0};\n    REDIS_OPTIONS_SET_TCP(&options, ip, port);\n    options.endpoint.tcp.source_addr = source_addr;\n    options.options |= REDIS_OPT_NONBLOCK;\n    return redisConnectWithOptions(&options);\n}\n\nredisContext *redisConnectBindNonBlockWithReuse(const char *ip, int port,\n                                                const char *source_addr) {\n    redisOptions options = {0};\n    REDIS_OPTIONS_SET_TCP(&options, ip, port);\n    options.endpoint.tcp.source_addr = source_addr;\n    options.options |= REDIS_OPT_NONBLOCK|REDIS_OPT_REUSEADDR;\n    return redisConnectWithOptions(&options);\n}\n\nredisContext *redisConnectUnix(const char *path) {\n    redisOptions options = {0};\n    REDIS_OPTIONS_SET_UNIX(&options, path);\n    return redisConnectWithOptions(&options);\n}\n\nredisContext *redisConnectUnixWithTimeout(const char *path, const struct timeval tv) {\n    redisOptions options = {0};\n    REDIS_OPTIONS_SET_UNIX(&options, path);\n    options.connect_timeout = &tv;\n    return redisConnectWithOptions(&options);\n}\n\nredisContext *redisConnectUnixNonBlock(const char *path) {\n    redisOptions options = {0};\n    REDIS_OPTIONS_SET_UNIX(&options, path);\n    options.options |= REDIS_OPT_NONBLOCK;\n    return redisConnectWithOptions(&options);\n}\n\nredisContext *redisConnectFd(redisFD fd) {\n    redisOptions options = {0};\n    options.type = REDIS_CONN_USERFD;\n    options.endpoint.fd = fd;\n    return redisConnectWithOptions(&options);\n}\n\n/* Set read/write timeout on a blocking socket. */\nint redisSetTimeout(redisContext *c, const struct timeval tv) {\n    if (c->flags & REDIS_BLOCK)\n        return redisContextSetTimeout(c,tv);\n    return REDIS_ERR;\n}\n\nint redisEnableKeepAliveWithInterval(redisContext *c, int interval) {\n    return redisKeepAlive(c, interval);\n}\n\n/* Enable connection KeepAlive. */\nint redisEnableKeepAlive(redisContext *c) {\n    return redisKeepAlive(c, REDIS_KEEPALIVE_INTERVAL);\n}\n\n/* Set the socket option TCP_USER_TIMEOUT. */\nint redisSetTcpUserTimeout(redisContext *c, unsigned int timeout) {\n    return redisContextSetTcpUserTimeout(c, timeout);\n}\n\n/* Set a user provided RESP3 PUSH handler and return any old one set. */\nredisPushFn *redisSetPushCallback(redisContext *c, redisPushFn *fn) {\n    redisPushFn *old = c->push_cb;\n    c->push_cb = fn;\n    return old;\n}\n\n/* Use this function to handle a read event on the descriptor. It will try\n * and read some bytes from the socket and feed them to the reply parser.\n *\n * After this function is called, you may use redisGetReplyFromReader to\n * see if there is a reply available. */\nint redisBufferRead(redisContext *c) {\n    char buf[1024*16];\n    int nread;\n\n    /* Return early when the context has seen an error. */\n    if (c->err)\n        return REDIS_ERR;\n\n    nread = c->funcs->read(c, buf, sizeof(buf));\n    if (nread < 0) {\n        return REDIS_ERR;\n    }\n    if (nread > 0 && redisReaderFeed(c->reader, buf, nread) != REDIS_OK) {\n        __redisSetError(c, c->reader->err, c->reader->errstr);\n        return REDIS_ERR;\n    }\n    return REDIS_OK;\n}\n\n/* Write the output buffer to the socket.\n *\n * Returns REDIS_OK when the buffer is empty, or (a part of) the buffer was\n * successfully written to the socket. When the buffer is empty after the\n * write operation, \"done\" is set to 1 (if given).\n *\n * Returns REDIS_ERR if an unrecoverable error occurred in the underlying\n * c->funcs->write function.\n */\nint redisBufferWrite(redisContext *c, int *done) {\n\n    /* Return early when the context has seen an error. */\n    if (c->err)\n        return REDIS_ERR;\n\n    if (sdslen(c->obuf) > 0) {\n        ssize_t nwritten = c->funcs->write(c);\n        if (nwritten < 0) {\n            return REDIS_ERR;\n        } else if (nwritten > 0) {\n            if (nwritten == (ssize_t)sdslen(c->obuf)) {\n                sdsfree(c->obuf);\n                c->obuf = sdsempty();\n                if (c->obuf == NULL)\n                    goto oom;\n            } else {\n                if (sdsrange(c->obuf,nwritten,-1) < 0) goto oom;\n            }\n        }\n    }\n    if (done != NULL) *done = (sdslen(c->obuf) == 0);\n    return REDIS_OK;\n\noom:\n    __redisSetError(c, REDIS_ERR_OOM, \"Out of memory\");\n    return REDIS_ERR;\n}\n\n/* Internal helper that returns 1 if the reply was a RESP3 PUSH\n * message and we handled it with a user-provided callback. */\nstatic int redisHandledPushReply(redisContext *c, void *reply) {\n    if (reply && c->push_cb && redisIsPushReply(reply)) {\n        c->push_cb(c->privdata, reply);\n        return 1;\n    }\n\n    return 0;\n}\n\n/* Get a reply from our reader or set an error in the context. */\nint redisGetReplyFromReader(redisContext *c, void **reply) {\n    if (redisReaderGetReply(c->reader, reply) == REDIS_ERR) {\n        __redisSetError(c,c->reader->err,c->reader->errstr);\n        return REDIS_ERR;\n    }\n\n    return REDIS_OK;\n}\n\n/* Internal helper to get the next reply from our reader while handling\n * any PUSH messages we encounter along the way.  This is separate from\n * redisGetReplyFromReader so as to not change its behavior. */\nstatic int redisNextInBandReplyFromReader(redisContext *c, void **reply) {\n    do {\n        if (redisGetReplyFromReader(c, reply) == REDIS_ERR)\n            return REDIS_ERR;\n    } while (redisHandledPushReply(c, *reply));\n\n    return REDIS_OK;\n}\n\nint redisGetReply(redisContext *c, void **reply) {\n    int wdone = 0;\n    void *aux = NULL;\n\n    /* Try to read pending replies */\n    if (redisNextInBandReplyFromReader(c,&aux) == REDIS_ERR)\n        return REDIS_ERR;\n\n    /* For the blocking context, flush output buffer and read reply */\n    if (aux == NULL && c->flags & REDIS_BLOCK) {\n        /* Write until done */\n        do {\n            if (redisBufferWrite(c,&wdone) == REDIS_ERR)\n                return REDIS_ERR;\n        } while (!wdone);\n\n        /* Read until there is a reply */\n        do {\n            if (redisBufferRead(c) == REDIS_ERR)\n                return REDIS_ERR;\n\n            if (redisNextInBandReplyFromReader(c,&aux) == REDIS_ERR)\n                return REDIS_ERR;\n        } while (aux == NULL);\n    }\n\n    /* Set reply or free it if we were passed NULL */\n    if (reply != NULL) {\n        *reply = aux;\n    } else {\n        freeReplyObject(aux);\n    }\n\n    return REDIS_OK;\n}\n\n\n/* Helper function for the redisAppendCommand* family of functions.\n *\n * Write a formatted command to the output buffer. When this family\n * is used, you need to call redisGetReply yourself to retrieve\n * the reply (or replies in pub/sub).\n */\nint __redisAppendCommand(redisContext *c, const char *cmd, size_t len) {\n    sds newbuf;\n\n    newbuf = sdscatlen(c->obuf,cmd,len);\n    if (newbuf == NULL) {\n        __redisSetError(c,REDIS_ERR_OOM,\"Out of memory\");\n        return REDIS_ERR;\n    }\n\n    c->obuf = newbuf;\n    return REDIS_OK;\n}\n\nint redisAppendFormattedCommand(redisContext *c, const char *cmd, size_t len) {\n\n    if (__redisAppendCommand(c, cmd, len) != REDIS_OK) {\n        return REDIS_ERR;\n    }\n\n    return REDIS_OK;\n}\n\nint redisvAppendCommand(redisContext *c, const char *format, va_list ap) {\n    char *cmd;\n    int len;\n\n    len = redisvFormatCommand(&cmd,format,ap);\n    if (len == -1) {\n        __redisSetError(c,REDIS_ERR_OOM,\"Out of memory\");\n        return REDIS_ERR;\n    } else if (len == -2) {\n        __redisSetError(c,REDIS_ERR_OTHER,\"Invalid format string\");\n        return REDIS_ERR;\n    }\n\n    if (__redisAppendCommand(c,cmd,len) != REDIS_OK) {\n        hi_free(cmd);\n        return REDIS_ERR;\n    }\n\n    hi_free(cmd);\n    return REDIS_OK;\n}\n\nint redisAppendCommand(redisContext *c, const char *format, ...) {\n    va_list ap;\n    int ret;\n\n    va_start(ap,format);\n    ret = redisvAppendCommand(c,format,ap);\n    va_end(ap);\n    return ret;\n}\n\nint redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen) {\n    sds cmd;\n    long long len;\n\n    len = redisFormatSdsCommandArgv(&cmd,argc,argv,argvlen);\n    if (len == -1) {\n        __redisSetError(c,REDIS_ERR_OOM,\"Out of memory\");\n        return REDIS_ERR;\n    }\n\n    if (__redisAppendCommand(c,cmd,len) != REDIS_OK) {\n        sdsfree(cmd);\n        return REDIS_ERR;\n    }\n\n    sdsfree(cmd);\n    return REDIS_OK;\n}\n\n/* Helper function for the redisCommand* family of functions.\n *\n * Write a formatted command to the output buffer. If the given context is\n * blocking, immediately read the reply into the \"reply\" pointer. When the\n * context is non-blocking, the \"reply\" pointer will not be used and the\n * command is simply appended to the write buffer.\n *\n * Returns the reply when a reply was successfully retrieved. Returns NULL\n * otherwise. When NULL is returned in a blocking context, the error field\n * in the context will be set.\n */\nstatic void *__redisBlockForReply(redisContext *c) {\n    void *reply;\n\n    if (c->flags & REDIS_BLOCK) {\n        if (redisGetReply(c,&reply) != REDIS_OK)\n            return NULL;\n        return reply;\n    }\n    return NULL;\n}\n\nvoid *redisvCommand(redisContext *c, const char *format, va_list ap) {\n    if (redisvAppendCommand(c,format,ap) != REDIS_OK)\n        return NULL;\n    return __redisBlockForReply(c);\n}\n\nvoid *redisCommand(redisContext *c, const char *format, ...) {\n    va_list ap;\n    va_start(ap,format);\n    void *reply = redisvCommand(c,format,ap);\n    va_end(ap);\n    return reply;\n}\n\nvoid *redisCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen) {\n    if (redisAppendCommandArgv(c,argc,argv,argvlen) != REDIS_OK)\n        return NULL;\n    return __redisBlockForReply(c);\n}\n"
  },
  {
    "path": "hiredis.h",
    "content": "/*\n * Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com>\n * Copyright (c) 2010-2014, Pieter Noordhuis <pcnoordhuis at gmail dot com>\n * Copyright (c) 2015, Matt Stancliff <matt at genges dot com>,\n *                     Jan-Erik Rediger <janerik at fnordig dot com>\n *\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n *   * Redistributions of source code must retain the above copyright notice,\n *     this list of conditions and the following disclaimer.\n *   * Redistributions in binary form must reproduce the above copyright\n *     notice, this list of conditions and the following disclaimer in the\n *     documentation and/or other materials provided with the distribution.\n *   * Neither the name of Redis nor the names of its contributors may be used\n *     to endorse or promote products derived from this software without\n *     specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE\n * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n */\n\n#ifndef __HIREDIS_H\n#define __HIREDIS_H\n#include \"read.h\"\n#include <stdarg.h> /* for va_list */\n#ifndef _MSC_VER\n#include <sys/time.h> /* for struct timeval */\n#else\nstruct timeval; /* forward declaration */\ntypedef intptr_t ssize_t;\n#endif\n#include <stdint.h> /* uintXX_t, etc */\n#include \"sds.h\" /* for sds */\n#include \"alloc.h\" /* for allocation wrappers */\n\n#define HIREDIS_MAJOR 1\n#define HIREDIS_MINOR 3\n#define HIREDIS_PATCH 0\n#define HIREDIS_SONAME 1.3.0\n\n/* Connection type can be blocking or non-blocking and is set in the\n * least significant bit of the flags field in redisContext. */\n#define REDIS_BLOCK 0x1\n\n/* Connection may be disconnected before being free'd. The second bit\n * in the flags field is set when the context is connected. */\n#define REDIS_CONNECTED 0x2\n\n/* The async API might try to disconnect cleanly and flush the output\n * buffer and read all subsequent replies before disconnecting.\n * This flag means no new commands can come in and the connection\n * should be terminated once all replies have been read. */\n#define REDIS_DISCONNECTING 0x4\n\n/* Flag specific to the async API which means that the context should be clean\n * up as soon as possible. */\n#define REDIS_FREEING 0x8\n\n/* Flag that is set when an async callback is executed. */\n#define REDIS_IN_CALLBACK 0x10\n\n/* Flag that is set when the async context has one or more subscriptions. */\n#define REDIS_SUBSCRIBED 0x20\n\n/* Flag that is set when monitor mode is active */\n#define REDIS_MONITORING 0x40\n\n/* Flag that is set when we should set SO_REUSEADDR before calling bind() */\n#define REDIS_REUSEADDR 0x80\n\n/* Flag that is set when the async connection supports push replies. */\n#define REDIS_SUPPORTS_PUSH 0x100\n\n/**\n * Flag that indicates the user does not want the context to\n * be automatically freed upon error\n */\n#define REDIS_NO_AUTO_FREE 0x200\n\n/* Flag that indicates the user does not want replies to be automatically freed */\n#define REDIS_NO_AUTO_FREE_REPLIES 0x400\n\n/* Flags to prefer IPv6 or IPv4 when doing DNS lookup. (If both are set,\n * AF_UNSPEC is used.) */\n#define REDIS_PREFER_IPV4 0x800\n#define REDIS_PREFER_IPV6 0x1000\n\n#define REDIS_KEEPALIVE_INTERVAL 15 /* seconds */\n\n/* number of times we retry to connect in the case of EADDRNOTAVAIL and\n * SO_REUSEADDR is being used. */\n#define REDIS_CONNECT_RETRIES  10\n\n/* Forward declarations for structs defined elsewhere */\nstruct redisAsyncContext;\nstruct redisContext;\n\n/* RESP3 push helpers and callback prototypes */\n#define redisIsPushReply(r) (((redisReply*)(r))->type == REDIS_REPLY_PUSH)\ntypedef void (redisPushFn)(void *, void *);\ntypedef void (redisAsyncPushFn)(struct redisAsyncContext *, void *);\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n/* This is the reply object returned by redisCommand() */\ntypedef struct redisReply {\n    int type; /* REDIS_REPLY_* */\n    long long integer; /* The integer when type is REDIS_REPLY_INTEGER */\n    double dval; /* The double when type is REDIS_REPLY_DOUBLE */\n    size_t len; /* Length of string */\n    char *str; /* Used for REDIS_REPLY_ERROR, REDIS_REPLY_STRING\n                  REDIS_REPLY_VERB, REDIS_REPLY_DOUBLE (in additional to dval),\n                  and REDIS_REPLY_BIGNUM. */\n    char vtype[4]; /* Used for REDIS_REPLY_VERB, contains the null\n                      terminated 3 character content type, such as \"txt\". */\n    size_t elements; /* number of elements, for REDIS_REPLY_ARRAY */\n    struct redisReply **element; /* elements vector for REDIS_REPLY_ARRAY */\n} redisReply;\n\nredisReader *redisReaderCreate(void);\n\n/* Function to free the reply objects hiredis returns by default. */\nvoid freeReplyObject(void *reply);\n\n/* Functions to format a command according to the protocol. */\nint redisvFormatCommand(char **target, const char *format, va_list ap);\nint redisFormatCommand(char **target, const char *format, ...);\nlong long redisFormatCommandArgv(char **target, int argc, const char **argv, const size_t *argvlen);\nlong long redisFormatSdsCommandArgv(sds *target, int argc, const char ** argv, const size_t *argvlen);\nvoid redisFreeCommand(char *cmd);\nvoid redisFreeSdsCommand(sds cmd);\n\nenum redisConnectionType {\n    REDIS_CONN_TCP,\n    REDIS_CONN_UNIX,\n    REDIS_CONN_USERFD\n};\n\nstruct redisSsl;\n\n#define REDIS_OPT_NONBLOCK 0x01\n#define REDIS_OPT_REUSEADDR 0x02\n#define REDIS_OPT_NOAUTOFREE 0x04        /* Don't automatically free the async\n                                          * object on a connection failure, or\n                                          * other implicit conditions. Only free\n                                          * on an explicit call to disconnect()\n                                          * or free() */\n#define REDIS_OPT_NO_PUSH_AUTOFREE 0x08  /* Don't automatically intercept and\n                                          * free RESP3 PUSH replies. */\n#define REDIS_OPT_NOAUTOFREEREPLIES 0x10 /* Don't automatically free replies. */\n#define REDIS_OPT_PREFER_IPV4 0x20       /* Prefer IPv4 in DNS lookups. */\n#define REDIS_OPT_PREFER_IPV6 0x40       /* Prefer IPv6 in DNS lookups. */\n#define REDIS_OPT_PREFER_IP_UNSPEC (REDIS_OPT_PREFER_IPV4 | REDIS_OPT_PREFER_IPV6)\n#define REDIS_OPT_SET_SOCK_CLOEXEC 0x80  /* Set SOCK_CLOEXEC on socket file descriptor. */\n\n/* In Unix systems a file descriptor is a regular signed int, with -1\n * representing an invalid descriptor. In Windows it is a SOCKET\n * (32- or 64-bit unsigned integer depending on the architecture), where\n * all bits set (~0) is INVALID_SOCKET.  */\n#ifndef _WIN32\ntypedef int redisFD;\n#define REDIS_INVALID_FD -1\n#else\n#ifdef _WIN64\ntypedef unsigned long long redisFD; /* SOCKET = 64-bit UINT_PTR */\n#else\ntypedef unsigned long redisFD;      /* SOCKET = 32-bit UINT_PTR */\n#endif\n#define REDIS_INVALID_FD ((redisFD)(~0)) /* INVALID_SOCKET */\n#endif\n\ntypedef struct {\n    /*\n     * the type of connection to use. This also indicates which\n     * `endpoint` member field to use\n     */\n    int type;\n    /* bit field of REDIS_OPT_xxx */\n    int options;\n    /* timeout value for connect operation. If NULL, no timeout is used */\n    const struct timeval *connect_timeout;\n    /* timeout value for commands. If NULL, no timeout is used.  This can be\n     * updated at runtime with redisSetTimeout/redisAsyncSetTimeout. */\n    const struct timeval *command_timeout;\n    union {\n        /** use this field for tcp/ip connections */\n        struct {\n            const char *source_addr;\n            const char *ip;\n            int port;\n        } tcp;\n        /** use this field for unix domain sockets */\n        const char *unix_socket;\n        /**\n         * use this field to have hiredis operate an already-open\n         * file descriptor */\n        redisFD fd;\n    } endpoint;\n\n    /* Optional user defined data/destructor */\n    void *privdata;\n    void (*free_privdata)(void *);\n\n    /* A user defined PUSH message callback */\n    redisPushFn *push_cb;\n    redisAsyncPushFn *async_push_cb;\n} redisOptions;\n\n/**\n * Helper macros to initialize options to their specified fields.\n */\n#define REDIS_OPTIONS_SET_TCP(opts, ip_, port_) do { \\\n        (opts)->type = REDIS_CONN_TCP;               \\\n        (opts)->endpoint.tcp.ip = ip_;               \\\n        (opts)->endpoint.tcp.port = port_;           \\\n    } while(0)\n\n#define REDIS_OPTIONS_SET_UNIX(opts, path) do { \\\n        (opts)->type = REDIS_CONN_UNIX;         \\\n        (opts)->endpoint.unix_socket = path;    \\\n    } while(0)\n\n#define REDIS_OPTIONS_SET_PRIVDATA(opts, data, dtor) do {  \\\n        (opts)->privdata = data;                           \\\n        (opts)->free_privdata = dtor;                      \\\n    } while(0)\n\ntypedef struct redisContextFuncs {\n    void (*close)(struct redisContext *);\n    void (*free_privctx)(void *);\n    void (*async_read)(struct redisAsyncContext *);\n    void (*async_write)(struct redisAsyncContext *);\n\n    /* Read/Write data to the underlying communication stream, returning the\n     * number of bytes read/written.  In the event of an unrecoverable error\n     * these functions shall return a value < 0.  In the event of a\n     * recoverable error, they should return 0. */\n    ssize_t (*read)(struct redisContext *, char *, size_t);\n    ssize_t (*write)(struct redisContext *);\n} redisContextFuncs;\n\n\n/* Context for a connection to Redis */\ntypedef struct redisContext {\n    const redisContextFuncs *funcs;   /* Function table */\n\n    int err; /* Error flags, 0 when there is no error */\n    char errstr[128]; /* String representation of error when applicable */\n    redisFD fd;\n    int flags;\n    char *obuf; /* Write buffer */\n    redisReader *reader; /* Protocol reader */\n\n    enum redisConnectionType connection_type;\n    struct timeval *connect_timeout;\n    struct timeval *command_timeout;\n\n    struct {\n        char *host;\n        char *source_addr;\n        int port;\n    } tcp;\n\n    struct {\n        char *path;\n    } unix_sock;\n\n    /* For non-blocking connect */\n    struct sockaddr *saddr;\n    size_t addrlen;\n\n    /* Optional data and corresponding destructor users can use to provide\n     * context to a given redisContext.  Not used by hiredis. */\n    void *privdata;\n    void (*free_privdata)(void *);\n\n    /* Internal context pointer presently used by hiredis to manage\n     * SSL connections. */\n    void *privctx;\n\n    /* An optional RESP3 PUSH handler */\n    redisPushFn *push_cb;\n} redisContext;\n\nredisContext *redisConnectWithOptions(const redisOptions *options);\nredisContext *redisConnect(const char *ip, int port);\nredisContext *redisConnectWithTimeout(const char *ip, int port, const struct timeval tv);\nredisContext *redisConnectNonBlock(const char *ip, int port);\nredisContext *redisConnectBindNonBlock(const char *ip, int port,\n                                       const char *source_addr);\nredisContext *redisConnectBindNonBlockWithReuse(const char *ip, int port,\n                                                const char *source_addr);\nredisContext *redisConnectUnix(const char *path);\nredisContext *redisConnectUnixWithTimeout(const char *path, const struct timeval tv);\nredisContext *redisConnectUnixNonBlock(const char *path);\nredisContext *redisConnectFd(redisFD fd);\n\n/**\n * Reconnect the given context using the saved information.\n *\n * This re-uses the exact same connect options as in the initial connection.\n * host, ip (or path), timeout and bind address are reused,\n * flags are used unmodified from the existing context.\n *\n * Returns REDIS_OK on successful connect or REDIS_ERR otherwise.\n */\nint redisReconnect(redisContext *c);\n\nredisPushFn *redisSetPushCallback(redisContext *c, redisPushFn *fn);\nint redisSetTimeout(redisContext *c, const struct timeval tv);\nint redisEnableKeepAlive(redisContext *c);\nint redisEnableKeepAliveWithInterval(redisContext *c, int interval);\nint redisSetTcpUserTimeout(redisContext *c, unsigned int timeout);\nvoid redisFree(redisContext *c);\nredisFD redisFreeKeepFd(redisContext *c);\nint redisBufferRead(redisContext *c);\nint redisBufferWrite(redisContext *c, int *done);\n\n/* In a blocking context, this function first checks if there are unconsumed\n * replies to return and returns one if so. Otherwise, it flushes the output\n * buffer to the socket and reads until it has a reply. In a non-blocking\n * context, it will return unconsumed replies until there are no more. */\nint redisGetReply(redisContext *c, void **reply);\nint redisGetReplyFromReader(redisContext *c, void **reply);\n\n/* Write a formatted command to the output buffer. Use these functions in blocking mode\n * to get a pipeline of commands. */\nint redisAppendFormattedCommand(redisContext *c, const char *cmd, size_t len);\n\n/* Write a command to the output buffer. Use these functions in blocking mode\n * to get a pipeline of commands. */\nint redisvAppendCommand(redisContext *c, const char *format, va_list ap);\nint redisAppendCommand(redisContext *c, const char *format, ...);\nint redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen);\n\n/* Issue a command to Redis. In a blocking context, it is identical to calling\n * redisAppendCommand, followed by redisGetReply. The function will return\n * NULL if there was an error in performing the request, otherwise it will\n * return the reply. In a non-blocking context, it is identical to calling\n * only redisAppendCommand and will always return NULL. */\nvoid *redisvCommand(redisContext *c, const char *format, va_list ap);\nvoid *redisCommand(redisContext *c, const char *format, ...);\nvoid *redisCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen);\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif\n"
  },
  {
    "path": "hiredis.pc.in",
    "content": "prefix=@CMAKE_INSTALL_PREFIX@\ninstall_libdir=@CMAKE_INSTALL_LIBDIR@\nexec_prefix=${prefix}\nlibdir=${exec_prefix}/${install_libdir}\nincludedir=${prefix}/include\npkgincludedir=${includedir}/hiredis\n\nName: hiredis\nDescription: Minimalistic C client library for Redis.\nVersion: @PROJECT_VERSION@\nLibs: -L${libdir} -lhiredis\nCflags: -I${pkgincludedir} -I${includedir} -D_FILE_OFFSET_BITS=64\n"
  },
  {
    "path": "hiredis.targets",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project ToolsVersion=\"4.0\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n  <ItemDefinitionGroup>\n    <ClCompile>\n      <AdditionalIncludeDirectories>$(MSBuildThisFileDirectory)\\..\\..\\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\n    </ClCompile>\n    <Link>\n      <AdditionalLibraryDirectories>$(MSBuildThisFileDirectory)\\..\\..\\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>\n    </Link>\n  </ItemDefinitionGroup>\n</Project>"
  },
  {
    "path": "hiredis_ssl-config.cmake.in",
    "content": "@PACKAGE_INIT@\n\nset_and_check(hiredis_ssl_INCLUDEDIR \"@PACKAGE_INCLUDE_INSTALL_DIR@\")\n\ninclude(CMakeFindDependencyMacro)\nfind_dependency(OpenSSL)\n\nIF (NOT TARGET hiredis::hiredis_ssl)\n\tINCLUDE(${CMAKE_CURRENT_LIST_DIR}/hiredis_ssl-targets.cmake)\nENDIF()\n\nSET(hiredis_ssl_LIBRARIES hiredis::hiredis_ssl)\nSET(hiredis_ssl_INCLUDE_DIRS ${hiredis_ssl_INCLUDEDIR})\n\ncheck_required_components(hiredis_ssl)\n\n"
  },
  {
    "path": "hiredis_ssl.h",
    "content": "\n/*\n * Copyright (c) 2019, Redis Labs\n *\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n *   * Redistributions of source code must retain the above copyright notice,\n *     this list of conditions and the following disclaimer.\n *   * Redistributions in binary form must reproduce the above copyright\n *     notice, this list of conditions and the following disclaimer in the\n *     documentation and/or other materials provided with the distribution.\n *   * Neither the name of Redis nor the names of its contributors may be used\n *     to endorse or promote products derived from this software without\n *     specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE\n * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n */\n\n#ifndef __HIREDIS_SSL_H\n#define __HIREDIS_SSL_H\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n/* This is the underlying struct for SSL in ssl.h, which is not included to\n * keep build dependencies short here.\n */\nstruct ssl_st;\n\n/* A wrapper around OpenSSL SSL_CTX to allow easy SSL use without directly\n * calling OpenSSL.\n */\ntypedef struct redisSSLContext redisSSLContext;\n\n/**\n * Initialization errors that redisCreateSSLContext() may return.\n */\n\ntypedef enum {\n    REDIS_SSL_CTX_NONE = 0,                     /* No Error */\n    REDIS_SSL_CTX_CREATE_FAILED,                /* Failed to create OpenSSL SSL_CTX */\n    REDIS_SSL_CTX_CERT_KEY_REQUIRED,            /* Client cert and key must both be specified or skipped */\n    REDIS_SSL_CTX_CA_CERT_LOAD_FAILED,          /* Failed to load CA Certificate or CA Path */\n    REDIS_SSL_CTX_CLIENT_CERT_LOAD_FAILED,      /* Failed to load client certificate */\n    REDIS_SSL_CTX_CLIENT_DEFAULT_CERT_FAILED,   /* Failed to set client default certificate directory */\n    REDIS_SSL_CTX_PRIVATE_KEY_LOAD_FAILED,      /* Failed to load private key */\n    REDIS_SSL_CTX_OS_CERTSTORE_OPEN_FAILED,     /* Failed to open system certificate store */\n    REDIS_SSL_CTX_OS_CERT_ADD_FAILED            /* Failed to add CA certificates obtained from system to the SSL context */\n} redisSSLContextError;\n\n/* Constants that mirror OpenSSL's verify modes. By default,\n * REDIS_SSL_VERIFY_PEER is used with redisCreateSSLContext().\n * Some Redis clients disable peer verification if there are no\n * certificates specified.\n */\n#define REDIS_SSL_VERIFY_NONE 0x00\n#define REDIS_SSL_VERIFY_PEER 0x01\n#define REDIS_SSL_VERIFY_FAIL_IF_NO_PEER_CERT 0x02\n#define REDIS_SSL_VERIFY_CLIENT_ONCE 0x04\n#define REDIS_SSL_VERIFY_POST_HANDSHAKE 0x08\n\n/* Options to create an OpenSSL context. */\ntypedef struct {\n    const char *cacert_filename;\n    const char *capath;\n    const char *cert_filename;\n    const char *private_key_filename;\n    const char *server_name;\n    int verify_mode;\n} redisSSLOptions;\n\n/**\n * Return the error message corresponding with the specified error code.\n */\n\nconst char *redisSSLContextGetError(redisSSLContextError error);\n\n/**\n * Helper function to initialize the OpenSSL library.\n *\n * OpenSSL requires one-time initialization before it can be used. Callers should\n * call this function only once, and only if OpenSSL is not directly initialized\n * elsewhere.\n */\nint redisInitOpenSSL(void);\n\n/**\n * Helper function to initialize an OpenSSL context that can be used\n * to initiate SSL connections.\n *\n * cacert_filename is an optional name of a CA certificate/bundle file to load\n * and use for validation.\n *\n * capath is an optional directory path where trusted CA certificate files are\n * stored in an OpenSSL-compatible structure.\n *\n * cert_filename and private_key_filename are optional names of a client side\n * certificate and private key files to use for authentication. They need to\n * be both specified or omitted.\n *\n * server_name is an optional and will be used as a server name indication\n * (SNI) TLS extension.\n *\n * If error is non-null, it will be populated in case the context creation fails\n * (returning a NULL).\n */\n\nredisSSLContext *redisCreateSSLContext(const char *cacert_filename, const char *capath,\n        const char *cert_filename, const char *private_key_filename,\n        const char *server_name, redisSSLContextError *error);\n\n/**\n  * Helper function to initialize an OpenSSL context that can be used\n  * to initiate SSL connections. This is a more extensible version of redisCreateSSLContext().\n  *\n  * options contains a structure of SSL options to use.\n  *\n  * If error is non-null, it will be populated in case the context creation fails\n  * (returning a NULL).\n*/\nredisSSLContext *redisCreateSSLContextWithOptions(redisSSLOptions *options,\n        redisSSLContextError *error);\n\n/**\n * Free a previously created OpenSSL context.\n */\nvoid redisFreeSSLContext(redisSSLContext *redis_ssl_ctx);\n\n/**\n * Initiate SSL on an existing redisContext.\n *\n * This is similar to redisInitiateSSL() but does not require the caller\n * to directly interact with OpenSSL, and instead uses a redisSSLContext\n * previously created using redisCreateSSLContext().\n */\n\nint redisInitiateSSLWithContext(redisContext *c, redisSSLContext *redis_ssl_ctx);\n\n/**\n * Initiate SSL/TLS negotiation on a provided OpenSSL SSL object.\n */\n\nint redisInitiateSSL(redisContext *c, struct ssl_st *ssl);\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif  /* __HIREDIS_SSL_H */\n"
  },
  {
    "path": "hiredis_ssl.pc.in",
    "content": "prefix=@CMAKE_INSTALL_PREFIX@\ninstall_libdir=@CMAKE_INSTALL_LIBDIR@\nexec_prefix=${prefix}\nlibdir=${exec_prefix}/${install_libdir}\nincludedir=${prefix}/include\npkgincludedir=${includedir}/hiredis\n\nName: hiredis_ssl\nDescription: SSL Support for hiredis.\nVersion: @PROJECT_VERSION@\nRequires: hiredis\nLibs: -L${libdir} -lhiredis_ssl\nLibs.private: -lssl -lcrypto\n"
  },
  {
    "path": "net.c",
    "content": "/* Extracted from anet.c to work properly with Hiredis error reporting.\n *\n * Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com>\n * Copyright (c) 2010-2014, Pieter Noordhuis <pcnoordhuis at gmail dot com>\n * Copyright (c) 2015, Matt Stancliff <matt at genges dot com>,\n *                     Jan-Erik Rediger <janerik at fnordig dot com>\n *\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n *   * Redistributions of source code must retain the above copyright notice,\n *     this list of conditions and the following disclaimer.\n *   * Redistributions in binary form must reproduce the above copyright\n *     notice, this list of conditions and the following disclaimer in the\n *     documentation and/or other materials provided with the distribution.\n *   * Neither the name of Redis nor the names of its contributors may be used\n *     to endorse or promote products derived from this software without\n *     specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE\n * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n */\n\n#include \"fmacros.h\"\n#include <sys/types.h>\n#include <fcntl.h>\n#include <string.h>\n#include <errno.h>\n#include <stdarg.h>\n#include <stdio.h>\n#include <limits.h>\n#include <stdlib.h>\n#include <time.h>\n\n#include \"net.h\"\n#include \"sds.h\"\n#include \"sockcompat.h\"\n#include \"win32.h\"\n\n/* Defined in hiredis.c */\nvoid __redisSetError(redisContext *c, int type, const char *str);\n\nint redisContextUpdateCommandTimeout(redisContext *c, const struct timeval *timeout);\n\nvoid redisNetClose(redisContext *c) {\n    if (c && c->fd != REDIS_INVALID_FD) {\n        close(c->fd);\n        c->fd = REDIS_INVALID_FD;\n    }\n}\n\nssize_t redisNetRead(redisContext *c, char *buf, size_t bufcap) {\n    ssize_t nread = recv(c->fd, buf, bufcap, 0);\n    if (nread == -1) {\n        if ((errno == EWOULDBLOCK && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) {\n            /* Try again later */\n            return 0;\n        } else if(errno == ETIMEDOUT && (c->flags & REDIS_BLOCK)) {\n            /* especially in windows */\n            __redisSetError(c, REDIS_ERR_TIMEOUT, \"recv timeout\");\n            return -1;\n        } else {\n            __redisSetError(c, REDIS_ERR_IO, strerror(errno));\n            return -1;\n        }\n    } else if (nread == 0) {\n        __redisSetError(c, REDIS_ERR_EOF, \"Server closed the connection\");\n        return -1;\n    } else {\n        return nread;\n    }\n}\n\nssize_t redisNetWrite(redisContext *c) {\n    ssize_t nwritten;\n\n    nwritten = send(c->fd, c->obuf, sdslen(c->obuf), 0);\n    if (nwritten < 0) {\n        if ((errno == EWOULDBLOCK && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) {\n            /* Try again */\n            return 0;\n        } else {\n            __redisSetError(c, REDIS_ERR_IO, strerror(errno));\n            return -1;\n        }\n    }\n\n    return nwritten;\n}\n\nstatic void __redisSetErrorFromErrno(redisContext *c, int type, const char *prefix) {\n    int errorno = errno;  /* snprintf() may change errno */\n    char buf[128] = { 0 };\n    size_t len = 0;\n\n    if (prefix != NULL)\n        len = snprintf(buf,sizeof(buf),\"%s: \",prefix);\n    strerror_r(errorno, (char *)(buf + len), sizeof(buf) - len);\n    __redisSetError(c,type,buf);\n}\n\nstatic int redisSetReuseAddr(redisContext *c) {\n    int on = 1;\n    if (setsockopt(c->fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) {\n        __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);\n        redisNetClose(c);\n        return REDIS_ERR;\n    }\n    return REDIS_OK;\n}\n\nstatic int redisCreateSocket(redisContext *c, int type) {\n    redisFD s;\n    int flags = SOCK_STREAM;\n\n#ifdef SOCK_CLOEXEC\n    if (c->flags & REDIS_OPT_SET_SOCK_CLOEXEC) {\n        flags |= SOCK_CLOEXEC;\n    }\n#endif\n\n    if ((s = socket(type, flags, 0)) == REDIS_INVALID_FD) {\n        __redisSetErrorFromErrno(c, REDIS_ERR_IO, NULL);\n        return REDIS_ERR;\n    }\n    c->fd = s;\n\n    if (type == AF_INET) {\n        if (redisSetReuseAddr(c) == REDIS_ERR) {\n            return REDIS_ERR;\n        }\n    }\n    return REDIS_OK;\n}\n\nstatic int redisSetBlocking(redisContext *c, int blocking) {\n#ifndef _WIN32\n    int flags;\n\n    /* Set the socket nonblocking.\n     * Note that fcntl(2) for F_GETFL and F_SETFL can't be\n     * interrupted by a signal. */\n    if ((flags = fcntl(c->fd, F_GETFL)) == -1) {\n        __redisSetErrorFromErrno(c,REDIS_ERR_IO,\"fcntl(F_GETFL)\");\n        redisNetClose(c);\n        return REDIS_ERR;\n    }\n\n    if (blocking)\n        flags &= ~O_NONBLOCK;\n    else\n        flags |= O_NONBLOCK;\n\n    if (fcntl(c->fd, F_SETFL, flags) == -1) {\n        __redisSetErrorFromErrno(c,REDIS_ERR_IO,\"fcntl(F_SETFL)\");\n        redisNetClose(c);\n        return REDIS_ERR;\n    }\n#else\n    u_long mode = blocking ? 0 : 1;\n    if (ioctl(c->fd, FIONBIO, &mode) == -1) {\n        __redisSetErrorFromErrno(c, REDIS_ERR_IO, \"ioctl(FIONBIO)\");\n        redisNetClose(c);\n        return REDIS_ERR;\n    }\n#endif /* _WIN32 */\n    return REDIS_OK;\n}\n\nint redisKeepAlive(redisContext *c, int interval) {\n    int val = 1;\n    redisFD fd = c->fd;\n\n    /* TCP_KEEPALIVE makes no sense with AF_UNIX connections */\n    if (c->connection_type == REDIS_CONN_UNIX)\n        return REDIS_ERR;\n\n#ifndef _WIN32\n    if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &val, sizeof(val)) == -1){\n        __redisSetError(c,REDIS_ERR_OTHER,strerror(errno));\n        return REDIS_ERR;\n    }\n\n    val = interval;\n\n#if defined(__APPLE__) && defined(__MACH__)\n    if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPALIVE, &val, sizeof(val)) < 0) {\n        __redisSetError(c,REDIS_ERR_OTHER,strerror(errno));\n        return REDIS_ERR;\n    }\n#else\n#if defined(__GLIBC__) && !defined(__FreeBSD_kernel__)\n    if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE, &val, sizeof(val)) < 0) {\n        __redisSetError(c,REDIS_ERR_OTHER,strerror(errno));\n        return REDIS_ERR;\n    }\n\n    val = interval/3;\n    if (val == 0) val = 1;\n    if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPINTVL, &val, sizeof(val)) < 0) {\n        __redisSetError(c,REDIS_ERR_OTHER,strerror(errno));\n        return REDIS_ERR;\n    }\n\n    val = 3;\n    if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPCNT, &val, sizeof(val)) < 0) {\n        __redisSetError(c,REDIS_ERR_OTHER,strerror(errno));\n        return REDIS_ERR;\n    }\n#endif\n#endif\n#else\n    int res;\n\n    res = win32_redisKeepAlive(fd, interval * 1000);\n    if (res != 0) {\n        __redisSetError(c, REDIS_ERR_OTHER, strerror(res));\n        return REDIS_ERR;\n    }\n#endif\n    return REDIS_OK;\n}\n\nint redisSetTcpNoDelay(redisContext *c) {\n    int yes = 1;\n    if (setsockopt(c->fd, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof(yes)) == -1) {\n        __redisSetErrorFromErrno(c,REDIS_ERR_IO,\"setsockopt(TCP_NODELAY)\");\n        redisNetClose(c);\n        return REDIS_ERR;\n    }\n    return REDIS_OK;\n}\n\nint redisContextSetTcpUserTimeout(redisContext *c, unsigned int timeout) {\n    int res;\n#ifdef TCP_USER_TIMEOUT\n    res = setsockopt(c->fd, IPPROTO_TCP, TCP_USER_TIMEOUT, &timeout, sizeof(timeout));\n#else\n    res = -1;\n    errno = ENOTSUP;\n    (void)timeout;\n#endif\n    if (res == -1) {\n        __redisSetErrorFromErrno(c,REDIS_ERR_IO,\"setsockopt(TCP_USER_TIMEOUT)\");\n        redisNetClose(c);\n        return REDIS_ERR;\n    }\n    return REDIS_OK;\n}\n\n#define __MAX_MSEC (((LONG_MAX) - 999) / 1000)\n\nstatic int redisContextTimeoutMsec(redisContext *c, long *result)\n{\n    const struct timeval *timeout = c->connect_timeout;\n    long msec = -1;\n\n    /* Only use timeout when not NULL. */\n    if (timeout != NULL) {\n        if (timeout->tv_usec > 1000000 || timeout->tv_sec > __MAX_MSEC) {\n            __redisSetError(c, REDIS_ERR_IO, \"Invalid timeout specified\");\n            *result = msec;\n            return REDIS_ERR;\n        }\n\n        msec = (timeout->tv_sec * 1000) + ((timeout->tv_usec + 999) / 1000);\n\n        if (msec < 0 || msec > INT_MAX) {\n            msec = INT_MAX;\n        }\n    }\n\n    *result = msec;\n    return REDIS_OK;\n}\n\nstatic long redisPollMillis(void) {\n#ifndef _MSC_VER\n    struct timespec now;\n    clock_gettime(CLOCK_MONOTONIC, &now);\n    return (now.tv_sec * 1000) + now.tv_nsec / 1000000;\n#else\n    FILETIME ft;\n    GetSystemTimeAsFileTime(&ft);\n    return (((long long)ft.dwHighDateTime << 32) | ft.dwLowDateTime) / 10;\n#endif\n}\n\nstatic int redisContextWaitReady(redisContext *c, long msec) {\n    struct pollfd wfd;\n    long end;\n    int res;\n\n    if (errno != EINPROGRESS) {\n        __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);\n        redisNetClose(c);\n        return REDIS_ERR;\n    }\n\n    wfd.fd = c->fd;\n    wfd.events = POLLOUT;\n    end = msec >= 0 ? redisPollMillis() + msec : 0;\n\n    while ((res = poll(&wfd, 1, msec)) <= 0) {\n        if (res < 0 && errno != EINTR) {\n            __redisSetErrorFromErrno(c, REDIS_ERR_IO, \"poll(2)\");\n            redisNetClose(c);\n            return REDIS_ERR;\n        } else if (res == 0 || (msec >= 0 && redisPollMillis() >= end)) {\n            errno = ETIMEDOUT;\n            __redisSetErrorFromErrno(c, REDIS_ERR_IO, NULL);\n            redisNetClose(c);\n            return REDIS_ERR;\n        } else {\n            /* res < 0 && errno == EINTR, try again */\n        }\n    }\n\n    if (redisCheckConnectDone(c, &res) != REDIS_OK || res == 0) {\n        redisCheckSocketError(c);\n        return REDIS_ERR;\n    }\n\n    return REDIS_OK;\n}\n\nint redisCheckConnectDone(redisContext *c, int *completed) {\n    int rc = connect(c->fd, (const struct sockaddr *)c->saddr, c->addrlen);\n    if (rc == 0) {\n        *completed = 1;\n        return REDIS_OK;\n    }\n    int error = errno;\n    if (error == EINPROGRESS) {\n        /* must check error to see if connect failed.  Get the socket error */\n        int fail, so_error;\n        socklen_t optlen = sizeof(so_error);\n        fail = getsockopt(c->fd, SOL_SOCKET, SO_ERROR, &so_error, &optlen);\n        if (fail == 0) {\n            if (so_error == 0) {\n                /* Socket is connected! */\n                *completed = 1;\n                return REDIS_OK;\n            }\n            /* connection error; */\n            errno = so_error;\n            error = so_error;\n        }\n    }\n    switch (error) {\n    case EISCONN:\n        *completed = 1;\n        return REDIS_OK;\n    case EALREADY:\n    case EWOULDBLOCK:\n        *completed = 0;\n        return REDIS_OK;\n    default:\n        return REDIS_ERR;\n    }\n}\n\nint redisCheckSocketError(redisContext *c) {\n    int err = 0, errno_saved = errno;\n    socklen_t errlen = sizeof(err);\n\n    if (getsockopt(c->fd, SOL_SOCKET, SO_ERROR, &err, &errlen) == -1) {\n        __redisSetErrorFromErrno(c,REDIS_ERR_IO,\"getsockopt(SO_ERROR)\");\n        return REDIS_ERR;\n    }\n\n    if (err == 0) {\n        err = errno_saved;\n    }\n\n    if (err) {\n        errno = err;\n        __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);\n        return REDIS_ERR;\n    }\n\n    return REDIS_OK;\n}\n\nint redisContextSetTimeout(redisContext *c, const struct timeval tv) {\n    const void *to_ptr = &tv;\n    size_t to_sz = sizeof(tv);\n\n    if (redisContextUpdateCommandTimeout(c, &tv) != REDIS_OK) {\n        __redisSetError(c, REDIS_ERR_OOM, \"Out of memory\");\n        return REDIS_ERR;\n    }\n    if (setsockopt(c->fd,SOL_SOCKET,SO_RCVTIMEO,to_ptr,to_sz) == -1) {\n        __redisSetErrorFromErrno(c,REDIS_ERR_IO,\"setsockopt(SO_RCVTIMEO)\");\n        return REDIS_ERR;\n    }\n    if (setsockopt(c->fd,SOL_SOCKET,SO_SNDTIMEO,to_ptr,to_sz) == -1) {\n        __redisSetErrorFromErrno(c,REDIS_ERR_IO,\"setsockopt(SO_SNDTIMEO)\");\n        return REDIS_ERR;\n    }\n    return REDIS_OK;\n}\n\nint redisContextUpdateConnectTimeout(redisContext *c, const struct timeval *timeout) {\n    /* Same timeval struct, short circuit */\n    if (c->connect_timeout == timeout)\n        return REDIS_OK;\n\n    /* Allocate context timeval if we need to */\n    if (c->connect_timeout == NULL) {\n        c->connect_timeout = hi_malloc(sizeof(*c->connect_timeout));\n        if (c->connect_timeout == NULL)\n            return REDIS_ERR;\n    }\n\n    memcpy(c->connect_timeout, timeout, sizeof(*c->connect_timeout));\n    return REDIS_OK;\n}\n\nint redisContextUpdateCommandTimeout(redisContext *c, const struct timeval *timeout) {\n    /* Same timeval struct, short circuit */\n    if (c->command_timeout == timeout)\n        return REDIS_OK;\n\n    /* Allocate context timeval if we need to */\n    if (c->command_timeout == NULL) {\n        c->command_timeout = hi_malloc(sizeof(*c->command_timeout));\n        if (c->command_timeout == NULL)\n            return REDIS_ERR;\n    }\n\n    memcpy(c->command_timeout, timeout, sizeof(*c->command_timeout));\n    return REDIS_OK;\n}\n\nstatic int _redisContextConnectTcp(redisContext *c, const char *addr, int port,\n                                   const struct timeval *timeout,\n                                   const char *source_addr) {\n    redisFD s;\n    int rv, n;\n    char _port[6];  /* strlen(\"65535\"); */\n    struct addrinfo hints, *servinfo, *bservinfo, *p, *b;\n    int blocking = (c->flags & REDIS_BLOCK);\n    int reuseaddr = (c->flags & REDIS_REUSEADDR);\n    int reuses = 0;\n    long timeout_msec = -1;\n\n    servinfo = NULL;\n    c->connection_type = REDIS_CONN_TCP;\n    c->tcp.port = port;\n\n    /* We need to take possession of the passed parameters\n     * to make them reusable for a reconnect.\n     * We also carefully check we don't free data we already own,\n     * as in the case of the reconnect method.\n     *\n     * This is a bit ugly, but atleast it works and doesn't leak memory.\n     **/\n    if (c->tcp.host != addr) {\n        hi_free(c->tcp.host);\n\n        c->tcp.host = hi_strdup(addr);\n        if (c->tcp.host == NULL)\n            goto oom;\n    }\n\n    if (timeout) {\n        if (redisContextUpdateConnectTimeout(c, timeout) == REDIS_ERR)\n            goto oom;\n    } else {\n        hi_free(c->connect_timeout);\n        c->connect_timeout = NULL;\n    }\n\n    if (redisContextTimeoutMsec(c, &timeout_msec) != REDIS_OK) {\n        goto error;\n    }\n\n    if (source_addr == NULL) {\n        hi_free(c->tcp.source_addr);\n        c->tcp.source_addr = NULL;\n    } else if (c->tcp.source_addr != source_addr) {\n        hi_free(c->tcp.source_addr);\n        c->tcp.source_addr = hi_strdup(source_addr);\n    }\n\n    snprintf(_port, 6, \"%d\", port);\n    memset(&hints,0,sizeof(hints));\n    hints.ai_family = AF_INET;\n    hints.ai_socktype = SOCK_STREAM;\n\n    /* DNS lookup. To use dual stack, set both flags to prefer both IPv4 and\n     * IPv6. By default, for historical reasons, we try IPv4 first and then we\n     * try IPv6 only if no IPv4 address was found. */\n    if (c->flags & REDIS_PREFER_IPV6 && c->flags & REDIS_PREFER_IPV4)\n        hints.ai_family = AF_UNSPEC;\n    else if (c->flags & REDIS_PREFER_IPV6)\n        hints.ai_family = AF_INET6;\n    else\n        hints.ai_family = AF_INET;\n\n    rv = getaddrinfo(c->tcp.host, _port, &hints, &servinfo);\n    if (rv != 0 && hints.ai_family != AF_UNSPEC) {\n        /* Try again with the other IP version. */\n        hints.ai_family = (hints.ai_family == AF_INET) ? AF_INET6 : AF_INET;\n        rv = getaddrinfo(c->tcp.host, _port, &hints, &servinfo);\n    }\n    if (rv != 0) {\n        __redisSetError(c, REDIS_ERR_OTHER, gai_strerror(rv));\n        return REDIS_ERR;\n    }\n    for (p = servinfo; p != NULL; p = p->ai_next) {\naddrretry: {\n        int sock_type = p->ai_socktype;\n\n#ifdef SOCK_CLOEXEC\n        if (c->flags & REDIS_OPT_SET_SOCK_CLOEXEC) {\n            sock_type |= SOCK_CLOEXEC;\n        }\n#endif\n\n        if ((s = socket(p->ai_family, sock_type, p->ai_protocol)) == REDIS_INVALID_FD)\n            continue;\n}\n\n        c->fd = s;\n\n        if (redisSetBlocking(c,0) != REDIS_OK)\n            goto error;\n        if (c->tcp.source_addr) {\n            int bound = 0;\n            /* Using getaddrinfo saves us from self-determining IPv4 vs IPv6 */\n            if ((rv = getaddrinfo(c->tcp.source_addr, NULL, &hints, &bservinfo)) != 0) {\n                char buf[128];\n                snprintf(buf,sizeof(buf),\"Can't get addr: %s\",gai_strerror(rv));\n                __redisSetError(c,REDIS_ERR_OTHER,buf);\n                goto error;\n            }\n\n            if (reuseaddr) {\n                n = 1;\n                if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char*) &n,\n                               sizeof(n)) < 0) {\n                    freeaddrinfo(bservinfo);\n                    goto error;\n                }\n            }\n\n            for (b = bservinfo; b != NULL; b = b->ai_next) {\n                if (bind(s,b->ai_addr,b->ai_addrlen) != -1) {\n                    bound = 1;\n                    break;\n                }\n            }\n            freeaddrinfo(bservinfo);\n            if (!bound) {\n                char buf[128];\n                snprintf(buf,sizeof(buf),\"Can't bind socket: %s\",strerror(errno));\n                __redisSetError(c,REDIS_ERR_OTHER,buf);\n                goto error;\n            }\n        }\n\n        /* For repeat connection */\n        hi_free(c->saddr);\n        c->saddr = hi_malloc(p->ai_addrlen);\n        if (c->saddr == NULL)\n            goto oom;\n\n        memcpy(c->saddr, p->ai_addr, p->ai_addrlen);\n        c->addrlen = p->ai_addrlen;\n\n        if (connect(s,p->ai_addr,p->ai_addrlen) == -1) {\n            if (errno == EHOSTUNREACH) {\n                redisNetClose(c);\n                continue;\n            } else if (errno == EINPROGRESS) {\n                if (blocking) {\n                    goto wait_for_ready;\n                }\n                /* This is ok.\n                 * Note that even when it's in blocking mode, we unset blocking\n                 * for `connect()`\n                 */\n            } else if (errno == EADDRNOTAVAIL && reuseaddr) {\n                if (++reuses >= REDIS_CONNECT_RETRIES) {\n                    goto error;\n                } else {\n                    redisNetClose(c);\n                    goto addrretry;\n                }\n            } else {\n                wait_for_ready:\n                if (redisContextWaitReady(c,timeout_msec) != REDIS_OK)\n                    goto error;\n                if (redisSetTcpNoDelay(c) != REDIS_OK)\n                    goto error;\n            }\n        }\n        if (blocking && redisSetBlocking(c,1) != REDIS_OK)\n            goto error;\n\n        c->flags |= REDIS_CONNECTED;\n        rv = REDIS_OK;\n        goto end;\n    }\n    if (p == NULL) {\n        char buf[128];\n        snprintf(buf,sizeof(buf),\"Can't create socket: %s\",strerror(errno));\n        __redisSetError(c,REDIS_ERR_OTHER,buf);\n        goto error;\n    }\n\noom:\n    __redisSetError(c, REDIS_ERR_OOM, \"Out of memory\");\nerror:\n    rv = REDIS_ERR;\nend:\n    if(servinfo) {\n        freeaddrinfo(servinfo);\n    }\n\n    return rv;  // Need to return REDIS_OK if alright\n}\n\nint redisContextConnectTcp(redisContext *c, const char *addr, int port,\n                           const struct timeval *timeout) {\n    return _redisContextConnectTcp(c, addr, port, timeout, NULL);\n}\n\nint redisContextConnectBindTcp(redisContext *c, const char *addr, int port,\n                               const struct timeval *timeout,\n                               const char *source_addr) {\n    return _redisContextConnectTcp(c, addr, port, timeout, source_addr);\n}\n\nint redisContextConnectUnix(redisContext *c, const char *path, const struct timeval *timeout) {\n#ifndef _WIN32\n    int blocking = (c->flags & REDIS_BLOCK);\n    struct sockaddr_un *sa;\n    long timeout_msec = -1;\n\n    if (redisCreateSocket(c,AF_UNIX) < 0)\n        return REDIS_ERR;\n    if (redisSetBlocking(c,0) != REDIS_OK)\n        return REDIS_ERR;\n\n    c->connection_type = REDIS_CONN_UNIX;\n    if (c->unix_sock.path != path) {\n        hi_free(c->unix_sock.path);\n\n        c->unix_sock.path = hi_strdup(path);\n        if (c->unix_sock.path == NULL)\n            goto oom;\n    }\n\n    if (timeout) {\n        if (redisContextUpdateConnectTimeout(c, timeout) == REDIS_ERR)\n            goto oom;\n    } else {\n        hi_free(c->connect_timeout);\n        c->connect_timeout = NULL;\n    }\n\n    if (redisContextTimeoutMsec(c,&timeout_msec) != REDIS_OK)\n        return REDIS_ERR;\n\n    /* Don't leak sockaddr if we're reconnecting */\n    if (c->saddr) hi_free(c->saddr);\n\n    sa = (struct sockaddr_un*)(c->saddr = hi_malloc(sizeof(struct sockaddr_un)));\n    if (sa == NULL)\n        goto oom;\n\n    c->addrlen = sizeof(struct sockaddr_un);\n    sa->sun_family = AF_UNIX;\n    strncpy(sa->sun_path, path, sizeof(sa->sun_path) - 1);\n    if (connect(c->fd, (struct sockaddr*)sa, sizeof(*sa)) == -1) {\n        if ((errno == EAGAIN || errno == EINPROGRESS) && !blocking) {\n            /* This is ok. */\n        } else {\n            if (redisContextWaitReady(c,timeout_msec) != REDIS_OK)\n                return REDIS_ERR;\n        }\n    }\n\n    /* Reset socket to be blocking after connect(2). */\n    if (blocking && redisSetBlocking(c,1) != REDIS_OK)\n        return REDIS_ERR;\n\n    c->flags |= REDIS_CONNECTED;\n    return REDIS_OK;\n#else\n    /* We currently do not support Unix sockets for Windows. */\n    /* TODO(m): https://devblogs.microsoft.com/commandline/af_unix-comes-to-windows/ */\n    errno = EPROTONOSUPPORT;\n    return REDIS_ERR;\n#endif /* _WIN32 */\noom:\n    __redisSetError(c, REDIS_ERR_OOM, \"Out of memory\");\n    return REDIS_ERR;\n}\n"
  },
  {
    "path": "net.h",
    "content": "/* Extracted from anet.c to work properly with Hiredis error reporting.\n *\n * Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com>\n * Copyright (c) 2010-2014, Pieter Noordhuis <pcnoordhuis at gmail dot com>\n * Copyright (c) 2015, Matt Stancliff <matt at genges dot com>,\n *                     Jan-Erik Rediger <janerik at fnordig dot com>\n *\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n *   * Redistributions of source code must retain the above copyright notice,\n *     this list of conditions and the following disclaimer.\n *   * Redistributions in binary form must reproduce the above copyright\n *     notice, this list of conditions and the following disclaimer in the\n *     documentation and/or other materials provided with the distribution.\n *   * Neither the name of Redis nor the names of its contributors may be used\n *     to endorse or promote products derived from this software without\n *     specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE\n * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n */\n\n#ifndef __NET_H\n#define __NET_H\n\n#include \"hiredis.h\"\n\nvoid redisNetClose(redisContext *c);\nssize_t redisNetRead(redisContext *c, char *buf, size_t bufcap);\nssize_t redisNetWrite(redisContext *c);\n\nint redisCheckSocketError(redisContext *c);\nint redisContextSetTimeout(redisContext *c, const struct timeval tv);\nint redisContextConnectTcp(redisContext *c, const char *addr, int port, const struct timeval *timeout);\nint redisContextConnectBindTcp(redisContext *c, const char *addr, int port,\n                               const struct timeval *timeout,\n                               const char *source_addr);\nint redisContextConnectUnix(redisContext *c, const char *path, const struct timeval *timeout);\nint redisKeepAlive(redisContext *c, int interval);\nint redisCheckConnectDone(redisContext *c, int *completed);\n\nint redisSetTcpNoDelay(redisContext *c);\nint redisContextSetTcpUserTimeout(redisContext *c, unsigned int timeout);\n\n#endif\n"
  },
  {
    "path": "read.c",
    "content": "/*\n * Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com>\n * Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com>\n *\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n *   * Redistributions of source code must retain the above copyright notice,\n *     this list of conditions and the following disclaimer.\n *   * Redistributions in binary form must reproduce the above copyright\n *     notice, this list of conditions and the following disclaimer in the\n *     documentation and/or other materials provided with the distribution.\n *   * Neither the name of Redis nor the names of its contributors may be used\n *     to endorse or promote products derived from this software without\n *     specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE\n * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n */\n\n#include \"fmacros.h\"\n#include <string.h>\n#include <stdlib.h>\n#ifndef _MSC_VER\n#include <unistd.h>\n#include <strings.h>\n#endif\n#include <assert.h>\n#include <errno.h>\n#include <ctype.h>\n#include <limits.h>\n#include <math.h>\n\n#include \"alloc.h\"\n#include \"read.h\"\n#include \"sds.h\"\n#include \"win32.h\"\n\n/* Initial size of our nested reply stack and how much we grow it when needd */\n#define REDIS_READER_STACK_SIZE 9\n\nstatic void __redisReaderSetError(redisReader *r, int type, const char *str) {\n    size_t len;\n\n    if (r->reply != NULL && r->fn && r->fn->freeObject) {\n        r->fn->freeObject(r->reply);\n        r->reply = NULL;\n    }\n\n    /* Clear input buffer on errors. */\n    sdsfree(r->buf);\n    r->buf = NULL;\n    r->pos = r->len = 0;\n\n    /* Reset task stack. */\n    r->ridx = -1;\n\n    /* Set error. */\n    r->err = type;\n    len = strlen(str);\n    len = len < (sizeof(r->errstr)-1) ? len : (sizeof(r->errstr)-1);\n    memcpy(r->errstr,str,len);\n    r->errstr[len] = '\\0';\n}\n\nstatic size_t chrtos(char *buf, size_t size, char byte) {\n    size_t len = 0;\n\n    switch(byte) {\n    case '\\\\':\n    case '\"':\n        len = snprintf(buf,size,\"\\\"\\\\%c\\\"\",byte);\n        break;\n    case '\\n': len = snprintf(buf,size,\"\\\"\\\\n\\\"\"); break;\n    case '\\r': len = snprintf(buf,size,\"\\\"\\\\r\\\"\"); break;\n    case '\\t': len = snprintf(buf,size,\"\\\"\\\\t\\\"\"); break;\n    case '\\a': len = snprintf(buf,size,\"\\\"\\\\a\\\"\"); break;\n    case '\\b': len = snprintf(buf,size,\"\\\"\\\\b\\\"\"); break;\n    default:\n        if (isprint(byte))\n            len = snprintf(buf,size,\"\\\"%c\\\"\",byte);\n        else\n            len = snprintf(buf,size,\"\\\"\\\\x%02x\\\"\",(unsigned char)byte);\n        break;\n    }\n\n    return len;\n}\n\nstatic void __redisReaderSetErrorProtocolByte(redisReader *r, char byte) {\n    char cbuf[8], sbuf[128];\n\n    chrtos(cbuf,sizeof(cbuf),byte);\n    snprintf(sbuf,sizeof(sbuf),\n        \"Protocol error, got %s as reply type byte\", cbuf);\n    __redisReaderSetError(r,REDIS_ERR_PROTOCOL,sbuf);\n}\n\nstatic void __redisReaderSetErrorOOM(redisReader *r) {\n    __redisReaderSetError(r,REDIS_ERR_OOM,\"Out of memory\");\n}\n\nstatic char *readBytes(redisReader *r, unsigned int bytes) {\n    char *p;\n    if (r->len-r->pos >= bytes) {\n        p = r->buf+r->pos;\n        r->pos += bytes;\n        return p;\n    }\n    return NULL;\n}\n\n/* Find pointer to \\r\\n. */\nstatic char *seekNewline(char *s, size_t len) {\n    char *ret;\n\n    /* We cannot match with fewer than 2 bytes */\n    if (len < 2)\n        return NULL;\n\n    /* Search up to len - 1 characters */\n    len--;\n\n    /* Look for the \\r */\n    while ((ret = memchr(s, '\\r', len)) != NULL) {\n        if (ret[1] == '\\n') {\n            /* Found. */\n            break;\n        }\n        /* Continue searching. */\n        ret++;\n        len -= ret - s;\n        s = ret;\n    }\n\n    return ret;\n}\n\n/* Convert a string into a long long. Returns REDIS_OK if the string could be\n * parsed into a (non-overflowing) long long, REDIS_ERR otherwise. The value\n * will be set to the parsed value when appropriate.\n *\n * Note that this function demands that the string strictly represents\n * a long long: no spaces or other characters before or after the string\n * representing the number are accepted, nor zeroes at the start if not\n * for the string \"0\" representing the zero number.\n *\n * Because of its strictness, it is safe to use this function to check if\n * you can convert a string into a long long, and obtain back the string\n * from the number without any loss in the string representation. */\nstatic int string2ll(const char *s, size_t slen, long long *value) {\n    const char *p = s;\n    size_t plen = 0;\n    int negative = 0;\n    unsigned long long v;\n\n    if (plen == slen)\n        return REDIS_ERR;\n\n    /* Special case: first and only digit is 0. */\n    if (slen == 1 && p[0] == '0') {\n        if (value != NULL) *value = 0;\n        return REDIS_OK;\n    }\n\n    if (p[0] == '-') {\n        negative = 1;\n        p++; plen++;\n\n        /* Abort on only a negative sign. */\n        if (plen == slen)\n            return REDIS_ERR;\n    }\n\n    /* First digit should be 1-9, otherwise the string should just be 0. */\n    if (p[0] >= '1' && p[0] <= '9') {\n        v = p[0]-'0';\n        p++; plen++;\n    } else if (p[0] == '0' && slen == 1) {\n        *value = 0;\n        return REDIS_OK;\n    } else {\n        return REDIS_ERR;\n    }\n\n    while (plen < slen && p[0] >= '0' && p[0] <= '9') {\n        if (v > (ULLONG_MAX / 10)) /* Overflow. */\n            return REDIS_ERR;\n        v *= 10;\n\n        if (v > (ULLONG_MAX - (p[0]-'0'))) /* Overflow. */\n            return REDIS_ERR;\n        v += p[0]-'0';\n\n        p++; plen++;\n    }\n\n    /* Return if not all bytes were used. */\n    if (plen < slen)\n        return REDIS_ERR;\n\n    if (negative) {\n        if (v > ((unsigned long long)(-(LLONG_MIN+1))+1)) /* Overflow. */\n            return REDIS_ERR;\n        if (value != NULL) *value = -v;\n    } else {\n        if (v > LLONG_MAX) /* Overflow. */\n            return REDIS_ERR;\n        if (value != NULL) *value = v;\n    }\n    return REDIS_OK;\n}\n\nstatic char *readLine(redisReader *r, int *_len) {\n    char *p, *s;\n    int len;\n\n    p = r->buf+r->pos;\n    s = seekNewline(p,(r->len-r->pos));\n    if (s != NULL) {\n        len = s-(r->buf+r->pos);\n        r->pos += len+2; /* skip \\r\\n */\n        if (_len) *_len = len;\n        return p;\n    }\n    return NULL;\n}\n\nstatic void moveToNextTask(redisReader *r) {\n    redisReadTask *cur, *prv;\n    while (r->ridx >= 0) {\n        /* Return a.s.a.p. when the stack is now empty. */\n        if (r->ridx == 0) {\n            r->ridx--;\n            return;\n        }\n\n        cur = r->task[r->ridx];\n        prv = r->task[r->ridx-1];\n        assert(prv->type == REDIS_REPLY_ARRAY ||\n               prv->type == REDIS_REPLY_MAP ||\n               prv->type == REDIS_REPLY_ATTR ||\n               prv->type == REDIS_REPLY_SET ||\n               prv->type == REDIS_REPLY_PUSH);\n        if (cur->idx == prv->elements-1) {\n            r->ridx--;\n        } else {\n            /* Reset the type because the next item can be anything */\n            assert(cur->idx < prv->elements);\n            cur->type = -1;\n            cur->elements = -1;\n            cur->idx++;\n            return;\n        }\n    }\n}\n\nstatic int processLineItem(redisReader *r) {\n    redisReadTask *cur = r->task[r->ridx];\n    void *obj;\n    char *p;\n    int len;\n\n    if ((p = readLine(r,&len)) != NULL) {\n        if (cur->type == REDIS_REPLY_INTEGER) {\n            long long v;\n\n            if (string2ll(p, len, &v) == REDIS_ERR) {\n                __redisReaderSetError(r,REDIS_ERR_PROTOCOL,\n                        \"Bad integer value\");\n                return REDIS_ERR;\n            }\n\n            if (r->fn && r->fn->createInteger) {\n                obj = r->fn->createInteger(cur,v);\n            } else {\n                obj = (void*)REDIS_REPLY_INTEGER;\n            }\n        } else if (cur->type == REDIS_REPLY_DOUBLE) {\n            char buf[326], *eptr;\n            double d;\n\n            if ((size_t)len >= sizeof(buf)) {\n                __redisReaderSetError(r,REDIS_ERR_PROTOCOL,\n                        \"Double value is too large\");\n                return REDIS_ERR;\n            }\n\n            memcpy(buf,p,len);\n            buf[len] = '\\0';\n\n            if (len == 3 && strcasecmp(buf,\"inf\") == 0) {\n                d = INFINITY; /* Positive infinite. */\n            } else if (len == 4 && strcasecmp(buf,\"-inf\") == 0) {\n                d = -INFINITY; /* Negative infinite. */\n            } else if ((len == 3 && strcasecmp(buf,\"nan\") == 0) ||\n                       (len == 4 && strcasecmp(buf, \"-nan\") == 0)) {\n                d = NAN; /* nan. */\n            } else {\n                d = strtod((char*)buf,&eptr);\n                /* RESP3 only allows \"inf\", \"-inf\", and finite values, while\n                 * strtod() allows other variations on infinity,\n                 * etc. We explicity handle our two allowed infinite cases and NaN\n                 * above, so strtod() should only result in finite values. */\n                if (buf[0] == '\\0' || eptr != &buf[len] || !isfinite(d)) {\n                    __redisReaderSetError(r,REDIS_ERR_PROTOCOL,\n                            \"Bad double value\");\n                    return REDIS_ERR;\n                }\n            }\n\n            if (r->fn && r->fn->createDouble) {\n                obj = r->fn->createDouble(cur,d,buf,len);\n            } else {\n                obj = (void*)REDIS_REPLY_DOUBLE;\n            }\n        } else if (cur->type == REDIS_REPLY_NIL) {\n            if (len != 0) {\n                __redisReaderSetError(r,REDIS_ERR_PROTOCOL,\n                        \"Bad nil value\");\n                return REDIS_ERR;\n            }\n\n            if (r->fn && r->fn->createNil)\n                obj = r->fn->createNil(cur);\n            else\n                obj = (void*)REDIS_REPLY_NIL;\n        } else if (cur->type == REDIS_REPLY_BOOL) {\n            int bval;\n\n            if (len != 1 || !strchr(\"tTfF\", p[0])) {\n                __redisReaderSetError(r,REDIS_ERR_PROTOCOL,\n                        \"Bad bool value\");\n                return REDIS_ERR;\n            }\n\n            bval = p[0] == 't' || p[0] == 'T';\n            if (r->fn && r->fn->createBool)\n                obj = r->fn->createBool(cur,bval);\n            else\n                obj = (void*)REDIS_REPLY_BOOL;\n        } else if (cur->type == REDIS_REPLY_BIGNUM) {\n            /* Ensure all characters are decimal digits (with possible leading\n             * minus sign). */\n            for (int i = 0; i < len; i++) {\n                /* XXX Consider: Allow leading '+'? Error on leading '0's? */\n                if (i == 0 && p[0] == '-') continue;\n                if (p[i] < '0' || p[i] > '9') {\n                    __redisReaderSetError(r,REDIS_ERR_PROTOCOL,\n                            \"Bad bignum value\");\n                    return REDIS_ERR;\n                }\n            }\n            if (r->fn && r->fn->createString)\n                obj = r->fn->createString(cur,p,len);\n            else\n                obj = (void*)REDIS_REPLY_BIGNUM;\n        } else {\n            /* Type will be error or status. */\n            for (int i = 0; i < len; i++) {\n                if (p[i] == '\\r' || p[i] == '\\n') {\n                    __redisReaderSetError(r,REDIS_ERR_PROTOCOL,\n                            \"Bad simple string value\");\n                    return REDIS_ERR;\n                }\n            }\n            if (r->fn && r->fn->createString)\n                obj = r->fn->createString(cur,p,len);\n            else\n                obj = (void*)(uintptr_t)(cur->type);\n        }\n\n        if (obj == NULL) {\n            __redisReaderSetErrorOOM(r);\n            return REDIS_ERR;\n        }\n\n        /* Set reply if this is the root object. */\n        if (r->ridx == 0) r->reply = obj;\n        moveToNextTask(r);\n        return REDIS_OK;\n    }\n\n    return REDIS_ERR;\n}\n\nstatic int processBulkItem(redisReader *r) {\n    redisReadTask *cur = r->task[r->ridx];\n    void *obj = NULL;\n    char *p, *s;\n    long long len;\n    unsigned long bytelen;\n    int success = 0;\n\n    p = r->buf+r->pos;\n    s = seekNewline(p,r->len-r->pos);\n    if (s != NULL) {\n        p = r->buf+r->pos;\n        bytelen = s-(r->buf+r->pos)+2; /* include \\r\\n */\n\n        if (string2ll(p, bytelen - 2, &len) == REDIS_ERR) {\n            __redisReaderSetError(r,REDIS_ERR_PROTOCOL,\n                    \"Bad bulk string length\");\n            return REDIS_ERR;\n        }\n\n        if (len < -1 || (LLONG_MAX > SIZE_MAX && len > (long long)SIZE_MAX)) {\n            __redisReaderSetError(r,REDIS_ERR_PROTOCOL,\n                    \"Bulk string length out of range\");\n            return REDIS_ERR;\n        }\n\n        if (len == -1) {\n            /* The nil object can always be created. */\n            if (r->fn && r->fn->createNil)\n                obj = r->fn->createNil(cur);\n            else\n                obj = (void*)REDIS_REPLY_NIL;\n            success = 1;\n        } else {\n            /* Only continue when the buffer contains the entire bulk item. */\n            bytelen += len+2; /* include \\r\\n */\n            if (r->pos+bytelen <= r->len) {\n                if ((cur->type == REDIS_REPLY_VERB && len < 4) ||\n                    (cur->type == REDIS_REPLY_VERB && s[5] != ':'))\n                {\n                    __redisReaderSetError(r,REDIS_ERR_PROTOCOL,\n                            \"Verbatim string 4 bytes of content type are \"\n                            \"missing or incorrectly encoded.\");\n                    return REDIS_ERR;\n                }\n                if (r->fn && r->fn->createString)\n                    obj = r->fn->createString(cur,s+2,len);\n                else\n                    obj = (void*)(uintptr_t)cur->type;\n                success = 1;\n            }\n        }\n\n        /* Proceed when obj was created. */\n        if (success) {\n            if (obj == NULL) {\n                __redisReaderSetErrorOOM(r);\n                return REDIS_ERR;\n            }\n\n            r->pos += bytelen;\n\n            /* Set reply if this is the root object. */\n            if (r->ridx == 0) r->reply = obj;\n            moveToNextTask(r);\n            return REDIS_OK;\n        }\n    }\n\n    return REDIS_ERR;\n}\n\nstatic int redisReaderGrow(redisReader *r) {\n    redisReadTask **aux;\n    int newlen;\n\n    /* Grow our stack size */\n    newlen = r->tasks + REDIS_READER_STACK_SIZE;\n    aux = hi_realloc(r->task, sizeof(*r->task) * newlen);\n    if (aux == NULL)\n        goto oom;\n\n    r->task = aux;\n\n    /* Allocate new tasks */\n    for (; r->tasks < newlen; r->tasks++) {\n        r->task[r->tasks] = hi_calloc(1, sizeof(**r->task));\n        if (r->task[r->tasks] == NULL)\n            goto oom;\n    }\n\n    return REDIS_OK;\noom:\n    __redisReaderSetErrorOOM(r);\n    return REDIS_ERR;\n}\n\n/* Process the array, map and set types. */\nstatic int processAggregateItem(redisReader *r) {\n    redisReadTask *cur = r->task[r->ridx];\n    void *obj;\n    char *p;\n    long long elements;\n    int root = 0, len;\n\n    if (r->ridx == r->tasks - 1) {\n        if (redisReaderGrow(r) == REDIS_ERR)\n            return REDIS_ERR;\n    }\n\n    if ((p = readLine(r,&len)) != NULL) {\n        if (string2ll(p, len, &elements) == REDIS_ERR) {\n            __redisReaderSetError(r,REDIS_ERR_PROTOCOL,\n                    \"Bad multi-bulk length\");\n            return REDIS_ERR;\n        }\n\n        root = (r->ridx == 0);\n\n        if (elements < -1 || (LLONG_MAX > SIZE_MAX && elements > SIZE_MAX) ||\n            (r->maxelements > 0 && elements > r->maxelements))\n        {\n            __redisReaderSetError(r,REDIS_ERR_PROTOCOL,\n                    \"Multi-bulk length out of range\");\n            return REDIS_ERR;\n        }\n\n        if (elements == -1) {\n            if (r->fn && r->fn->createNil)\n                obj = r->fn->createNil(cur);\n            else\n                obj = (void*)REDIS_REPLY_NIL;\n\n            if (obj == NULL) {\n                __redisReaderSetErrorOOM(r);\n                return REDIS_ERR;\n            }\n\n            moveToNextTask(r);\n        } else {\n            if (cur->type == REDIS_REPLY_MAP || cur->type == REDIS_REPLY_ATTR) elements *= 2;\n\n            if (r->fn && r->fn->createArray)\n                obj = r->fn->createArray(cur,elements);\n            else\n                obj = (void*)(uintptr_t)cur->type;\n\n            if (obj == NULL) {\n                __redisReaderSetErrorOOM(r);\n                return REDIS_ERR;\n            }\n\n            /* Modify task stack when there are more than 0 elements. */\n            if (elements > 0) {\n                cur->elements = elements;\n                cur->obj = obj;\n                r->ridx++;\n                r->task[r->ridx]->type = -1;\n                r->task[r->ridx]->elements = -1;\n                r->task[r->ridx]->idx = 0;\n                r->task[r->ridx]->obj = NULL;\n                r->task[r->ridx]->parent = cur;\n                r->task[r->ridx]->privdata = r->privdata;\n            } else {\n                moveToNextTask(r);\n            }\n        }\n\n        /* Set reply if this is the root object. */\n        if (root) r->reply = obj;\n        return REDIS_OK;\n    }\n\n    return REDIS_ERR;\n}\n\nstatic int processItem(redisReader *r) {\n    redisReadTask *cur = r->task[r->ridx];\n    char *p;\n\n    /* check if we need to read type */\n    if (cur->type < 0) {\n        if ((p = readBytes(r,1)) != NULL) {\n            switch (p[0]) {\n            case '-':\n                cur->type = REDIS_REPLY_ERROR;\n                break;\n            case '+':\n                cur->type = REDIS_REPLY_STATUS;\n                break;\n            case ':':\n                cur->type = REDIS_REPLY_INTEGER;\n                break;\n            case ',':\n                cur->type = REDIS_REPLY_DOUBLE;\n                break;\n            case '_':\n                cur->type = REDIS_REPLY_NIL;\n                break;\n            case '$':\n                cur->type = REDIS_REPLY_STRING;\n                break;\n            case '*':\n                cur->type = REDIS_REPLY_ARRAY;\n                break;\n            case '%':\n                cur->type = REDIS_REPLY_MAP;\n                break;\n            case '|':\n                cur->type = REDIS_REPLY_ATTR;\n                break;\n            case '~':\n                cur->type = REDIS_REPLY_SET;\n                break;\n            case '#':\n                cur->type = REDIS_REPLY_BOOL;\n                break;\n            case '=':\n                cur->type = REDIS_REPLY_VERB;\n                break;\n            case '>':\n                cur->type = REDIS_REPLY_PUSH;\n                break;\n            case '(':\n                cur->type = REDIS_REPLY_BIGNUM;\n                break;\n            default:\n                __redisReaderSetErrorProtocolByte(r,*p);\n                return REDIS_ERR;\n            }\n        } else {\n            /* could not consume 1 byte */\n            return REDIS_ERR;\n        }\n    }\n\n    /* process typed item */\n    switch(cur->type) {\n    case REDIS_REPLY_ERROR:\n    case REDIS_REPLY_STATUS:\n    case REDIS_REPLY_INTEGER:\n    case REDIS_REPLY_DOUBLE:\n    case REDIS_REPLY_NIL:\n    case REDIS_REPLY_BOOL:\n    case REDIS_REPLY_BIGNUM:\n        return processLineItem(r);\n    case REDIS_REPLY_STRING:\n    case REDIS_REPLY_VERB:\n        return processBulkItem(r);\n    case REDIS_REPLY_ARRAY:\n    case REDIS_REPLY_MAP:\n    case REDIS_REPLY_ATTR:\n    case REDIS_REPLY_SET:\n    case REDIS_REPLY_PUSH:\n        return processAggregateItem(r);\n    default:\n        assert(NULL);\n        return REDIS_ERR; /* Avoid warning. */\n    }\n}\n\nredisReader *redisReaderCreateWithFunctions(redisReplyObjectFunctions *fn) {\n    redisReader *r;\n\n    r = hi_calloc(1,sizeof(redisReader));\n    if (r == NULL)\n        return NULL;\n\n    r->buf = sdsempty();\n    if (r->buf == NULL)\n        goto oom;\n\n    r->task = hi_calloc(REDIS_READER_STACK_SIZE, sizeof(*r->task));\n    if (r->task == NULL)\n        goto oom;\n\n    for (; r->tasks < REDIS_READER_STACK_SIZE; r->tasks++) {\n        r->task[r->tasks] = hi_calloc(1, sizeof(**r->task));\n        if (r->task[r->tasks] == NULL)\n            goto oom;\n    }\n\n    r->fn = fn;\n    r->maxbuf = REDIS_READER_MAX_BUF;\n    r->maxelements = REDIS_READER_MAX_ARRAY_ELEMENTS;\n    r->ridx = -1;\n\n    return r;\noom:\n    redisReaderFree(r);\n    return NULL;\n}\n\nvoid redisReaderFree(redisReader *r) {\n    if (r == NULL)\n        return;\n\n    if (r->reply != NULL && r->fn && r->fn->freeObject)\n        r->fn->freeObject(r->reply);\n\n    if (r->task) {\n        /* We know r->task[i] is allocated if i < r->tasks */\n        for (int i = 0; i < r->tasks; i++) {\n            hi_free(r->task[i]);\n        }\n\n        hi_free(r->task);\n    }\n\n    sdsfree(r->buf);\n    hi_free(r);\n}\n\nint redisReaderFeed(redisReader *r, const char *buf, size_t len) {\n    sds newbuf;\n\n    /* Return early when this reader is in an erroneous state. */\n    if (r->err)\n        return REDIS_ERR;\n\n    /* Copy the provided buffer. */\n    if (buf != NULL && len >= 1) {\n        /* Destroy internal buffer when it is empty and is quite large. */\n        if (r->len == 0 && r->maxbuf != 0 && sdsavail(r->buf) > r->maxbuf) {\n            sdsfree(r->buf);\n            r->buf = sdsempty();\n            if (r->buf == 0) goto oom;\n\n            r->pos = 0;\n        }\n\n        newbuf = sdscatlen(r->buf,buf,len);\n        if (newbuf == NULL) goto oom;\n\n        r->buf = newbuf;\n        r->len = sdslen(r->buf);\n    }\n\n    return REDIS_OK;\noom:\n    __redisReaderSetErrorOOM(r);\n    return REDIS_ERR;\n}\n\nint redisReaderGetReply(redisReader *r, void **reply) {\n    /* Default target pointer to NULL. */\n    if (reply != NULL)\n        *reply = NULL;\n\n    /* Return early when this reader is in an erroneous state. */\n    if (r->err)\n        return REDIS_ERR;\n\n    /* When the buffer is empty, there will never be a reply. */\n    if (r->len == 0)\n        return REDIS_OK;\n\n    /* Set first item to process when the stack is empty. */\n    if (r->ridx == -1) {\n        r->task[0]->type = -1;\n        r->task[0]->elements = -1;\n        r->task[0]->idx = -1;\n        r->task[0]->obj = NULL;\n        r->task[0]->parent = NULL;\n        r->task[0]->privdata = r->privdata;\n        r->ridx = 0;\n    }\n\n    /* Process items in reply. */\n    while (r->ridx >= 0)\n        if (processItem(r) != REDIS_OK)\n            break;\n\n    /* Return ASAP when an error occurred. */\n    if (r->err)\n        return REDIS_ERR;\n\n    /* Discard part of the buffer when we've consumed at least 1k, to avoid\n     * doing unnecessary calls to memmove() in sds.c. */\n    if (r->pos >= 1024) {\n        if (sdsrange(r->buf,r->pos,-1) < 0) return REDIS_ERR;\n        r->pos = 0;\n        r->len = sdslen(r->buf);\n    }\n\n    /* Emit a reply when there is one. */\n    if (r->ridx == -1) {\n        if (reply != NULL) {\n            *reply = r->reply;\n        } else if (r->reply != NULL && r->fn && r->fn->freeObject) {\n            r->fn->freeObject(r->reply);\n        }\n        r->reply = NULL;\n    }\n    return REDIS_OK;\n}\n"
  },
  {
    "path": "read.h",
    "content": "/*\n * Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com>\n * Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com>\n *\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n *   * Redistributions of source code must retain the above copyright notice,\n *     this list of conditions and the following disclaimer.\n *   * Redistributions in binary form must reproduce the above copyright\n *     notice, this list of conditions and the following disclaimer in the\n *     documentation and/or other materials provided with the distribution.\n *   * Neither the name of Redis nor the names of its contributors may be used\n *     to endorse or promote products derived from this software without\n *     specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE\n * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n */\n\n\n#ifndef __HIREDIS_READ_H\n#define __HIREDIS_READ_H\n#include <stdio.h> /* for size_t */\n\n#define REDIS_ERR -1\n#define REDIS_OK 0\n\n/* When an error occurs, the err flag in a context is set to hold the type of\n * error that occurred. REDIS_ERR_IO means there was an I/O error and you\n * should use the \"errno\" variable to find out what is wrong.\n * For other values, the \"errstr\" field will hold a description. */\n#define REDIS_ERR_IO 1 /* Error in read or write */\n#define REDIS_ERR_EOF 3 /* End of file */\n#define REDIS_ERR_PROTOCOL 4 /* Protocol error */\n#define REDIS_ERR_OOM 5 /* Out of memory */\n#define REDIS_ERR_TIMEOUT 6 /* Timed out */\n#define REDIS_ERR_OTHER 2 /* Everything else... */\n\n#define REDIS_REPLY_STRING 1\n#define REDIS_REPLY_ARRAY 2\n#define REDIS_REPLY_INTEGER 3\n#define REDIS_REPLY_NIL 4\n#define REDIS_REPLY_STATUS 5\n#define REDIS_REPLY_ERROR 6\n#define REDIS_REPLY_DOUBLE 7\n#define REDIS_REPLY_BOOL 8\n#define REDIS_REPLY_MAP 9\n#define REDIS_REPLY_SET 10\n#define REDIS_REPLY_ATTR 11\n#define REDIS_REPLY_PUSH 12\n#define REDIS_REPLY_BIGNUM 13\n#define REDIS_REPLY_VERB 14\n\n/* Default max unused reader buffer. */\n#define REDIS_READER_MAX_BUF (1024*16)\n\n/* Default multi-bulk element limit */\n#define REDIS_READER_MAX_ARRAY_ELEMENTS ((1LL<<32) - 1)\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\ntypedef struct redisReadTask {\n    int type;\n    long long elements; /* number of elements in multibulk container */\n    int idx; /* index in parent (array) object */\n    void *obj; /* holds user-generated value for a read task */\n    struct redisReadTask *parent; /* parent task */\n    void *privdata; /* user-settable arbitrary field */\n} redisReadTask;\n\ntypedef struct redisReplyObjectFunctions {\n    void *(*createString)(const redisReadTask*, char*, size_t);\n    void *(*createArray)(const redisReadTask*, size_t);\n    void *(*createInteger)(const redisReadTask*, long long);\n    void *(*createDouble)(const redisReadTask*, double, char*, size_t);\n    void *(*createNil)(const redisReadTask*);\n    void *(*createBool)(const redisReadTask*, int);\n    void (*freeObject)(void*);\n} redisReplyObjectFunctions;\n\ntypedef struct redisReader {\n    int err; /* Error flags, 0 when there is no error */\n    char errstr[128]; /* String representation of error when applicable */\n\n    char *buf; /* Read buffer */\n    size_t pos; /* Buffer cursor */\n    size_t len; /* Buffer length */\n    size_t maxbuf; /* Max length of unused buffer */\n    long long maxelements; /* Max multi-bulk elements */\n\n    redisReadTask **task;\n    int tasks;\n\n    int ridx; /* Index of current read task */\n    void *reply; /* Temporary reply pointer */\n\n    redisReplyObjectFunctions *fn;\n    void *privdata;\n} redisReader;\n\n/* Public API for the protocol parser. */\nredisReader *redisReaderCreateWithFunctions(redisReplyObjectFunctions *fn);\nvoid redisReaderFree(redisReader *r);\nint redisReaderFeed(redisReader *r, const char *buf, size_t len);\nint redisReaderGetReply(redisReader *r, void **reply);\n\n#define redisReaderSetPrivdata(_r, _p) (int)(((redisReader*)(_r))->privdata = (_p))\n#define redisReaderGetObject(_r) (((redisReader*)(_r))->reply)\n#define redisReaderGetError(_r) (((redisReader*)(_r))->errstr)\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif\n"
  },
  {
    "path": "sds.c",
    "content": "/* SDSLib 2.0 -- A C dynamic strings library\n *\n * Copyright (c) 2006-2015, Salvatore Sanfilippo <antirez at gmail dot com>\n * Copyright (c) 2015, Oran Agra\n * Copyright (c) 2015, Redis Labs, Inc\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n *   * Redistributions of source code must retain the above copyright notice,\n *     this list of conditions and the following disclaimer.\n *   * Redistributions in binary form must reproduce the above copyright\n *     notice, this list of conditions and the following disclaimer in the\n *     documentation and/or other materials provided with the distribution.\n *   * Neither the name of Redis nor the names of its contributors may be used\n *     to endorse or promote products derived from this software without\n *     specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE\n * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n */\n\n#include \"fmacros.h\"\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <ctype.h>\n#include <assert.h>\n#include <limits.h>\n#include \"sds.h\"\n#include \"sdsalloc.h\"\n\nstatic inline int sdsHdrSize(char type) {\n    switch(type&SDS_TYPE_MASK) {\n        case SDS_TYPE_5:\n            return sizeof(struct sdshdr5);\n        case SDS_TYPE_8:\n            return sizeof(struct sdshdr8);\n        case SDS_TYPE_16:\n            return sizeof(struct sdshdr16);\n        case SDS_TYPE_32:\n            return sizeof(struct sdshdr32);\n        case SDS_TYPE_64:\n            return sizeof(struct sdshdr64);\n    }\n    return 0;\n}\n\nstatic inline char sdsReqType(size_t string_size) {\n    if (string_size < 32)\n        return SDS_TYPE_5;\n    if (string_size < 0xff)\n        return SDS_TYPE_8;\n    if (string_size < 0xffff)\n        return SDS_TYPE_16;\n    if (string_size < 0xffffffff)\n        return SDS_TYPE_32;\n    return SDS_TYPE_64;\n}\n\n/* Create a new sds string with the content specified by the 'init' pointer\n * and 'initlen'.\n * If NULL is used for 'init' the string is initialized with zero bytes.\n *\n * The string is always null-terminated (all the sds strings are, always) so\n * even if you create an sds string with:\n *\n * mystring = sdsnewlen(\"abc\",3);\n *\n * You can print the string with printf() as there is an implicit \\0 at the\n * end of the string. However the string is binary safe and can contain\n * \\0 characters in the middle, as the length is stored in the sds header. */\nsds sdsnewlen(const void *init, size_t initlen) {\n    void *sh;\n    sds s;\n    char type = sdsReqType(initlen);\n    /* Empty strings are usually created in order to append. Use type 8\n     * since type 5 is not good at this. */\n    if (type == SDS_TYPE_5 && initlen == 0) type = SDS_TYPE_8;\n    int hdrlen = sdsHdrSize(type);\n    unsigned char *fp; /* flags pointer. */\n\n    if (hdrlen+initlen+1 <= initlen) return NULL; /* Catch size_t overflow */\n    sh = s_malloc(hdrlen+initlen+1);\n    if (sh == NULL) return NULL;\n    if (!init)\n        memset(sh, 0, hdrlen+initlen+1);\n    s = (char*)sh+hdrlen;\n    fp = ((unsigned char*)s)-1;\n    switch(type) {\n        case SDS_TYPE_5: {\n            *fp = type | (initlen << SDS_TYPE_BITS);\n            break;\n        }\n        case SDS_TYPE_8: {\n            SDS_HDR_VAR(8,s);\n            sh->len = initlen;\n            sh->alloc = initlen;\n            *fp = type;\n            break;\n        }\n        case SDS_TYPE_16: {\n            SDS_HDR_VAR(16,s);\n            sh->len = initlen;\n            sh->alloc = initlen;\n            *fp = type;\n            break;\n        }\n        case SDS_TYPE_32: {\n            SDS_HDR_VAR(32,s);\n            sh->len = initlen;\n            sh->alloc = initlen;\n            *fp = type;\n            break;\n        }\n        case SDS_TYPE_64: {\n            SDS_HDR_VAR(64,s);\n            sh->len = initlen;\n            sh->alloc = initlen;\n            *fp = type;\n            break;\n        }\n    }\n    if (initlen && init)\n        memcpy(s, init, initlen);\n    s[initlen] = '\\0';\n    return s;\n}\n\n/* Create an empty (zero length) sds string. Even in this case the string\n * always has an implicit null term. */\nsds sdsempty(void) {\n    return sdsnewlen(\"\",0);\n}\n\n/* Create a new sds string starting from a null terminated C string. */\nsds sdsnew(const char *init) {\n    size_t initlen = (init == NULL) ? 0 : strlen(init);\n    return sdsnewlen(init, initlen);\n}\n\n/* Duplicate an sds string. */\nsds sdsdup(const sds s) {\n    return sdsnewlen(s, sdslen(s));\n}\n\n/* Free an sds string. No operation is performed if 's' is NULL. */\nvoid sdsfree(sds s) {\n    if (s == NULL) return;\n    s_free((char*)s-sdsHdrSize(s[-1]));\n}\n\n/* Set the sds string length to the length as obtained with strlen(), so\n * considering as content only up to the first null term character.\n *\n * This function is useful when the sds string is hacked manually in some\n * way, like in the following example:\n *\n * s = sdsnew(\"foobar\");\n * s[2] = '\\0';\n * sdsupdatelen(s);\n * printf(\"%d\\n\", sdslen(s));\n *\n * The output will be \"2\", but if we comment out the call to sdsupdatelen()\n * the output will be \"6\" as the string was modified but the logical length\n * remains 6 bytes. */\nvoid sdsupdatelen(sds s) {\n    size_t reallen = strlen(s);\n    sdssetlen(s, reallen);\n}\n\n/* Modify an sds string in-place to make it empty (zero length).\n * However all the existing buffer is not discarded but set as free space\n * so that next append operations will not require allocations up to the\n * number of bytes previously available. */\nvoid sdsclear(sds s) {\n    sdssetlen(s, 0);\n    s[0] = '\\0';\n}\n\n/* Enlarge the free space at the end of the sds string so that the caller\n * is sure that after calling this function can overwrite up to addlen\n * bytes after the end of the string, plus one more byte for nul term.\n *\n * Note: this does not change the *length* of the sds string as returned\n * by sdslen(), but only the free buffer space we have. */\nsds sdsMakeRoomFor(sds s, size_t addlen) {\n    void *sh, *newsh;\n    size_t avail = sdsavail(s);\n    size_t len, newlen, reqlen;\n    char type, oldtype = s[-1] & SDS_TYPE_MASK;\n    int hdrlen;\n\n    /* Return ASAP if there is enough space left. */\n    if (avail >= addlen) return s;\n\n    len = sdslen(s);\n    sh = (char*)s-sdsHdrSize(oldtype);\n    reqlen = newlen = (len+addlen);\n    if (newlen <= len) return NULL; /* Catch size_t overflow */\n    if (newlen < SDS_MAX_PREALLOC)\n        newlen *= 2;\n    else\n        newlen += SDS_MAX_PREALLOC;\n\n    type = sdsReqType(newlen);\n\n    /* Don't use type 5: the user is appending to the string and type 5 is\n     * not able to remember empty space, so sdsMakeRoomFor() must be called\n     * at every appending operation. */\n    if (type == SDS_TYPE_5) type = SDS_TYPE_8;\n\n    hdrlen = sdsHdrSize(type);\n    if (hdrlen+newlen+1 <= reqlen) return NULL; /* Catch size_t overflow */\n    if (oldtype==type) {\n        newsh = s_realloc(sh, hdrlen+newlen+1);\n        if (newsh == NULL) return NULL;\n        s = (char*)newsh+hdrlen;\n    } else {\n        /* Since the header size changes, need to move the string forward,\n         * and can't use realloc */\n        newsh = s_malloc(hdrlen+newlen+1);\n        if (newsh == NULL) return NULL;\n        memcpy((char*)newsh+hdrlen, s, len+1);\n        s_free(sh);\n        s = (char*)newsh+hdrlen;\n        s[-1] = type;\n        sdssetlen(s, len);\n    }\n    sdssetalloc(s, newlen);\n    return s;\n}\n\n/* Reallocate the sds string so that it has no free space at the end. The\n * contained string remains not altered, but next concatenation operations\n * will require a reallocation.\n *\n * After the call, the passed sds string is no longer valid and all the\n * references must be substituted with the new pointer returned by the call. */\nsds sdsRemoveFreeSpace(sds s) {\n    void *sh, *newsh;\n    char type, oldtype = s[-1] & SDS_TYPE_MASK;\n    int hdrlen;\n    size_t len = sdslen(s);\n    sh = (char*)s-sdsHdrSize(oldtype);\n\n    type = sdsReqType(len);\n    hdrlen = sdsHdrSize(type);\n    if (oldtype==type) {\n        newsh = s_realloc(sh, hdrlen+len+1);\n        if (newsh == NULL) return NULL;\n        s = (char*)newsh+hdrlen;\n    } else {\n        newsh = s_malloc(hdrlen+len+1);\n        if (newsh == NULL) return NULL;\n        memcpy((char*)newsh+hdrlen, s, len+1);\n        s_free(sh);\n        s = (char*)newsh+hdrlen;\n        s[-1] = type;\n        sdssetlen(s, len);\n    }\n    sdssetalloc(s, len);\n    return s;\n}\n\n/* Return the total size of the allocation of the specifed sds string,\n * including:\n * 1) The sds header before the pointer.\n * 2) The string.\n * 3) The free buffer at the end if any.\n * 4) The implicit null term.\n */\nsize_t sdsAllocSize(sds s) {\n    size_t alloc = sdsalloc(s);\n    return sdsHdrSize(s[-1])+alloc+1;\n}\n\n/* Return the pointer of the actual SDS allocation (normally SDS strings\n * are referenced by the start of the string buffer). */\nvoid *sdsAllocPtr(sds s) {\n    return (void*) (s-sdsHdrSize(s[-1]));\n}\n\n/* Increment the sds length and decrements the left free space at the\n * end of the string according to 'incr'. Also set the null term\n * in the new end of the string.\n *\n * This function is used in order to fix the string length after the\n * user calls sdsMakeRoomFor(), writes something after the end of\n * the current string, and finally needs to set the new length.\n *\n * Note: it is possible to use a negative increment in order to\n * right-trim the string.\n *\n * Usage example:\n *\n * Using sdsIncrLen() and sdsMakeRoomFor() it is possible to mount the\n * following schema, to cat bytes coming from the kernel to the end of an\n * sds string without copying into an intermediate buffer:\n *\n * oldlen = sdslen(s);\n * s = sdsMakeRoomFor(s, BUFFER_SIZE);\n * nread = read(fd, s+oldlen, BUFFER_SIZE);\n * ... check for nread <= 0 and handle it ...\n * sdsIncrLen(s, nread);\n */\nvoid sdsIncrLen(sds s, int incr) {\n    unsigned char flags = s[-1];\n    size_t len;\n    switch(flags&SDS_TYPE_MASK) {\n        case SDS_TYPE_5: {\n            unsigned char *fp = ((unsigned char*)s)-1;\n            unsigned char oldlen = SDS_TYPE_5_LEN(flags);\n            assert((incr > 0 && oldlen+incr < 32) || (incr < 0 && oldlen >= (unsigned int)(-incr)));\n            *fp = SDS_TYPE_5 | ((oldlen+incr) << SDS_TYPE_BITS);\n            len = oldlen+incr;\n            break;\n        }\n        case SDS_TYPE_8: {\n            SDS_HDR_VAR(8,s);\n            assert((incr >= 0 && sh->alloc-sh->len >= incr) || (incr < 0 && sh->len >= (unsigned int)(-incr)));\n            len = (sh->len += incr);\n            break;\n        }\n        case SDS_TYPE_16: {\n            SDS_HDR_VAR(16,s);\n            assert((incr >= 0 && sh->alloc-sh->len >= incr) || (incr < 0 && sh->len >= (unsigned int)(-incr)));\n            len = (sh->len += incr);\n            break;\n        }\n        case SDS_TYPE_32: {\n            SDS_HDR_VAR(32,s);\n            assert((incr >= 0 && sh->alloc-sh->len >= (unsigned int)incr) || (incr < 0 && sh->len >= (unsigned int)(-incr)));\n            len = (sh->len += incr);\n            break;\n        }\n        case SDS_TYPE_64: {\n            SDS_HDR_VAR(64,s);\n            assert((incr >= 0 && sh->alloc-sh->len >= (uint64_t)incr) || (incr < 0 && sh->len >= (uint64_t)(-incr)));\n            len = (sh->len += incr);\n            break;\n        }\n        default: len = 0; /* Just to avoid compilation warnings. */\n    }\n    s[len] = '\\0';\n}\n\n/* Grow the sds to have the specified length. Bytes that were not part of\n * the original length of the sds will be set to zero.\n *\n * if the specified length is smaller than the current length, no operation\n * is performed. */\nsds sdsgrowzero(sds s, size_t len) {\n    size_t curlen = sdslen(s);\n\n    if (len <= curlen) return s;\n    s = sdsMakeRoomFor(s,len-curlen);\n    if (s == NULL) return NULL;\n\n    /* Make sure added region doesn't contain garbage */\n    memset(s+curlen,0,(len-curlen+1)); /* also set trailing \\0 byte */\n    sdssetlen(s, len);\n    return s;\n}\n\n/* Append the specified binary-safe string pointed by 't' of 'len' bytes to the\n * end of the specified sds string 's'.\n *\n * After the call, the passed sds string is no longer valid and all the\n * references must be substituted with the new pointer returned by the call. */\nsds sdscatlen(sds s, const void *t, size_t len) {\n    size_t curlen = sdslen(s);\n\n    s = sdsMakeRoomFor(s,len);\n    if (s == NULL) return NULL;\n    memcpy(s+curlen, t, len);\n    sdssetlen(s, curlen+len);\n    s[curlen+len] = '\\0';\n    return s;\n}\n\n/* Append the specified null termianted C string to the sds string 's'.\n *\n * After the call, the passed sds string is no longer valid and all the\n * references must be substituted with the new pointer returned by the call. */\nsds sdscat(sds s, const char *t) {\n    return sdscatlen(s, t, strlen(t));\n}\n\n/* Append the specified sds 't' to the existing sds 's'.\n *\n * After the call, the modified sds string is no longer valid and all the\n * references must be substituted with the new pointer returned by the call. */\nsds sdscatsds(sds s, const sds t) {\n    return sdscatlen(s, t, sdslen(t));\n}\n\n/* Destructively modify the sds string 's' to hold the specified binary\n * safe string pointed by 't' of length 'len' bytes. */\nsds sdscpylen(sds s, const char *t, size_t len) {\n    if (sdsalloc(s) < len) {\n        s = sdsMakeRoomFor(s,len-sdslen(s));\n        if (s == NULL) return NULL;\n    }\n    memcpy(s, t, len);\n    s[len] = '\\0';\n    sdssetlen(s, len);\n    return s;\n}\n\n/* Like sdscpylen() but 't' must be a null-terminated string so that the length\n * of the string is obtained with strlen(). */\nsds sdscpy(sds s, const char *t) {\n    return sdscpylen(s, t, strlen(t));\n}\n\n/* Helper for sdscatlonglong() doing the actual number -> string\n * conversion. 's' must point to a string with room for at least\n * SDS_LLSTR_SIZE bytes.\n *\n * The function returns the length of the null-terminated string\n * representation stored at 's'. */\n#define SDS_LLSTR_SIZE 21\nint sdsll2str(char *s, long long value) {\n    char *p, aux;\n    unsigned long long v;\n    size_t l;\n\n    /* Generate the string representation, this method produces\n     * an reversed string. */\n    v = (value < 0) ? -value : value;\n    p = s;\n    do {\n        *p++ = '0'+(v%10);\n        v /= 10;\n    } while(v);\n    if (value < 0) *p++ = '-';\n\n    /* Compute length and add null term. */\n    l = p-s;\n    *p = '\\0';\n\n    /* Reverse the string. */\n    p--;\n    while(s < p) {\n        aux = *s;\n        *s = *p;\n        *p = aux;\n        s++;\n        p--;\n    }\n    return l;\n}\n\n/* Identical sdsll2str(), but for unsigned long long type. */\nint sdsull2str(char *s, unsigned long long v) {\n    char *p, aux;\n    size_t l;\n\n    /* Generate the string representation, this method produces\n     * an reversed string. */\n    p = s;\n    do {\n        *p++ = '0'+(v%10);\n        v /= 10;\n    } while(v);\n\n    /* Compute length and add null term. */\n    l = p-s;\n    *p = '\\0';\n\n    /* Reverse the string. */\n    p--;\n    while(s < p) {\n        aux = *s;\n        *s = *p;\n        *p = aux;\n        s++;\n        p--;\n    }\n    return l;\n}\n\n/* Create an sds string from a long long value. It is much faster than:\n *\n * sdscatprintf(sdsempty(),\"%lld\\n\", value);\n */\nsds sdsfromlonglong(long long value) {\n    char buf[SDS_LLSTR_SIZE];\n    int len = sdsll2str(buf,value);\n\n    return sdsnewlen(buf,len);\n}\n\n/* Like sdscatprintf() but gets va_list instead of being variadic. */\nsds sdscatvprintf(sds s, const char *fmt, va_list ap) {\n    va_list cpy;\n    char staticbuf[1024], *buf = staticbuf, *t;\n    size_t buflen = strlen(fmt)*2;\n\n    /* We try to start using a static buffer for speed.\n     * If not possible we revert to heap allocation. */\n    if (buflen > sizeof(staticbuf)) {\n        buf = s_malloc(buflen);\n        if (buf == NULL) return NULL;\n    } else {\n        buflen = sizeof(staticbuf);\n    }\n\n    /* Try with buffers two times bigger every time we fail to\n     * fit the string in the current buffer size. */\n    while(1) {\n        buf[buflen-2] = '\\0';\n        va_copy(cpy,ap);\n        vsnprintf(buf, buflen, fmt, cpy);\n        va_end(cpy);\n        if (buf[buflen-2] != '\\0') {\n            if (buf != staticbuf) s_free(buf);\n            buflen *= 2;\n            buf = s_malloc(buflen);\n            if (buf == NULL) return NULL;\n            continue;\n        }\n        break;\n    }\n\n    /* Finally concat the obtained string to the SDS string and return it. */\n    t = sdscat(s, buf);\n    if (buf != staticbuf) s_free(buf);\n    return t;\n}\n\n/* Append to the sds string 's' a string obtained using printf-alike format\n * specifier.\n *\n * After the call, the modified sds string is no longer valid and all the\n * references must be substituted with the new pointer returned by the call.\n *\n * Example:\n *\n * s = sdsnew(\"Sum is: \");\n * s = sdscatprintf(s,\"%d+%d = %d\",a,b,a+b).\n *\n * Often you need to create a string from scratch with the printf-alike\n * format. When this is the need, just use sdsempty() as the target string:\n *\n * s = sdscatprintf(sdsempty(), \"... your format ...\", args);\n */\nsds sdscatprintf(sds s, const char *fmt, ...) {\n    va_list ap;\n    char *t;\n    va_start(ap, fmt);\n    t = sdscatvprintf(s,fmt,ap);\n    va_end(ap);\n    return t;\n}\n\n/* This function is similar to sdscatprintf, but much faster as it does\n * not rely on sprintf() family functions implemented by the libc that\n * are often very slow. Moreover directly handling the sds string as\n * new data is concatenated provides a performance improvement.\n *\n * However this function only handles an incompatible subset of printf-alike\n * format specifiers:\n *\n * %s - C String\n * %S - SDS string\n * %i - signed int\n * %I - 64 bit signed integer (long long, int64_t)\n * %u - unsigned int\n * %U - 64 bit unsigned integer (unsigned long long, uint64_t)\n * %% - Verbatim \"%\" character.\n */\nsds sdscatfmt(sds s, char const *fmt, ...) {\n    const char *f = fmt;\n    long i;\n    va_list ap;\n\n    va_start(ap,fmt);\n    i = sdslen(s); /* Position of the next byte to write to dest str. */\n    while(*f) {\n        char next, *str;\n        size_t l;\n        long long num;\n        unsigned long long unum;\n\n        /* Make sure there is always space for at least 1 char. */\n        if (sdsavail(s)==0) {\n            s = sdsMakeRoomFor(s,1);\n            if (s == NULL) goto fmt_error;\n        }\n\n        switch(*f) {\n        case '%':\n            next = *(f+1);\n            f++;\n            switch(next) {\n            case 's':\n            case 'S':\n                str = va_arg(ap,char*);\n                l = (next == 's') ? strlen(str) : sdslen(str);\n                if (sdsavail(s) < l) {\n                    s = sdsMakeRoomFor(s,l);\n                    if (s == NULL) goto fmt_error;\n                }\n                memcpy(s+i,str,l);\n                sdsinclen(s,l);\n                i += l;\n                break;\n            case 'i':\n            case 'I':\n                if (next == 'i')\n                    num = va_arg(ap,int);\n                else\n                    num = va_arg(ap,long long);\n                {\n                    char buf[SDS_LLSTR_SIZE];\n                    l = sdsll2str(buf,num);\n                    if (sdsavail(s) < l) {\n                        s = sdsMakeRoomFor(s,l);\n                        if (s == NULL) goto fmt_error;\n                    }\n                    memcpy(s+i,buf,l);\n                    sdsinclen(s,l);\n                    i += l;\n                }\n                break;\n            case 'u':\n            case 'U':\n                if (next == 'u')\n                    unum = va_arg(ap,unsigned int);\n                else\n                    unum = va_arg(ap,unsigned long long);\n                {\n                    char buf[SDS_LLSTR_SIZE];\n                    l = sdsull2str(buf,unum);\n                    if (sdsavail(s) < l) {\n                        s = sdsMakeRoomFor(s,l);\n                        if (s == NULL) goto fmt_error;\n                    }\n                    memcpy(s+i,buf,l);\n                    sdsinclen(s,l);\n                    i += l;\n                }\n                break;\n            default: /* Handle %% and generally %<unknown>. */\n                s[i++] = next;\n                sdsinclen(s,1);\n                break;\n            }\n            break;\n        default:\n            s[i++] = *f;\n            sdsinclen(s,1);\n            break;\n        }\n        f++;\n    }\n    va_end(ap);\n\n    /* Add null-term */\n    s[i] = '\\0';\n    return s;\n\nfmt_error:\n    va_end(ap);\n    return NULL;\n}\n\n/* Remove the part of the string from left and from right composed just of\n * contiguous characters found in 'cset', that is a null terminted C string.\n *\n * After the call, the modified sds string is no longer valid and all the\n * references must be substituted with the new pointer returned by the call.\n *\n * Example:\n *\n * s = sdsnew(\"AA...AA.a.aa.aHelloWorld     :::\");\n * s = sdstrim(s,\"Aa. :\");\n * printf(\"%s\\n\", s);\n *\n * Output will be just \"Hello World\".\n */\nsds sdstrim(sds s, const char *cset) {\n    char *end, *sp, *ep;\n    size_t len;\n\n    sp = s;\n    ep = end = s+sdslen(s)-1;\n    while(sp <= end && strchr(cset, *sp)) sp++;\n    while(ep > sp && strchr(cset, *ep)) ep--;\n    len = (sp > ep) ? 0 : ((ep-sp)+1);\n    if (s != sp) memmove(s, sp, len);\n    s[len] = '\\0';\n    sdssetlen(s,len);\n    return s;\n}\n\n/* Turn the string into a smaller (or equal) string containing only the\n * substring specified by the 'start' and 'end' indexes.\n *\n * start and end can be negative, where -1 means the last character of the\n * string, -2 the penultimate character, and so forth.\n *\n * The interval is inclusive, so the start and end characters will be part\n * of the resulting string.\n *\n * The string is modified in-place.\n *\n * Return value:\n * -1 (error) if sdslen(s) is larger than maximum positive ssize_t value.\n *  0 on success.\n *\n * Example:\n *\n * s = sdsnew(\"Hello World\");\n * sdsrange(s,1,-1); => \"ello World\"\n */\nint sdsrange(sds s, ssize_t start, ssize_t end) {\n    size_t newlen, len = sdslen(s);\n    if (len > SSIZE_MAX) return -1;\n\n    if (len == 0) return 0;\n    if (start < 0) {\n        start = len+start;\n        if (start < 0) start = 0;\n    }\n    if (end < 0) {\n        end = len+end;\n        if (end < 0) end = 0;\n    }\n    newlen = (start > end) ? 0 : (end-start)+1;\n    if (newlen != 0) {\n        if (start >= (ssize_t)len) {\n            newlen = 0;\n        } else if (end >= (ssize_t)len) {\n            end = len-1;\n            newlen = (start > end) ? 0 : (end-start)+1;\n        }\n    } else {\n        start = 0;\n    }\n    if (start && newlen) memmove(s, s+start, newlen);\n    s[newlen] = 0;\n    sdssetlen(s,newlen);\n    return 0;\n}\n\n/* Apply tolower() to every character of the sds string 's'. */\nvoid sdstolower(sds s) {\n    size_t len = sdslen(s), j;\n\n    for (j = 0; j < len; j++) s[j] = tolower(s[j]);\n}\n\n/* Apply toupper() to every character of the sds string 's'. */\nvoid sdstoupper(sds s) {\n    size_t len = sdslen(s), j;\n\n    for (j = 0; j < len; j++) s[j] = toupper(s[j]);\n}\n\n/* Compare two sds strings s1 and s2 with memcmp().\n *\n * Return value:\n *\n *     positive if s1 > s2.\n *     negative if s1 < s2.\n *     0 if s1 and s2 are exactly the same binary string.\n *\n * If two strings share exactly the same prefix, but one of the two has\n * additional characters, the longer string is considered to be greater than\n * the smaller one. */\nint sdscmp(const sds s1, const sds s2) {\n    size_t l1, l2, minlen;\n    int cmp;\n\n    l1 = sdslen(s1);\n    l2 = sdslen(s2);\n    minlen = (l1 < l2) ? l1 : l2;\n    cmp = memcmp(s1,s2,minlen);\n    if (cmp == 0) return l1-l2;\n    return cmp;\n}\n\n/* Split 's' with separator in 'sep'. An array\n * of sds strings is returned. *count will be set\n * by reference to the number of tokens returned.\n *\n * On out of memory, zero length string, zero length\n * separator, NULL is returned.\n *\n * Note that 'sep' is able to split a string using\n * a multi-character separator. For example\n * sdssplit(\"foo_-_bar\",\"_-_\"); will return two\n * elements \"foo\" and \"bar\".\n *\n * This version of the function is binary-safe but\n * requires length arguments. sdssplit() is just the\n * same function but for zero-terminated strings.\n */\nsds *sdssplitlen(const char *s, int len, const char *sep, int seplen, int *count) {\n    int elements = 0, slots = 5, start = 0, j;\n    sds *tokens;\n\n    if (seplen < 1 || len < 0) return NULL;\n\n    tokens = s_malloc(sizeof(sds)*slots);\n    if (tokens == NULL) return NULL;\n\n    if (len == 0) {\n        *count = 0;\n        return tokens;\n    }\n    for (j = 0; j < (len-(seplen-1)); j++) {\n        /* make sure there is room for the next element and the final one */\n        if (slots < elements+2) {\n            sds *newtokens;\n\n            slots *= 2;\n            newtokens = s_realloc(tokens,sizeof(sds)*slots);\n            if (newtokens == NULL) goto cleanup;\n            tokens = newtokens;\n        }\n        /* search the separator */\n        if ((seplen == 1 && *(s+j) == sep[0]) || (memcmp(s+j,sep,seplen) == 0)) {\n            tokens[elements] = sdsnewlen(s+start,j-start);\n            if (tokens[elements] == NULL) goto cleanup;\n            elements++;\n            start = j+seplen;\n            j = j+seplen-1; /* skip the separator */\n        }\n    }\n    /* Add the final element. We are sure there is room in the tokens array. */\n    tokens[elements] = sdsnewlen(s+start,len-start);\n    if (tokens[elements] == NULL) goto cleanup;\n    elements++;\n    *count = elements;\n    return tokens;\n\ncleanup:\n    {\n        int i;\n        for (i = 0; i < elements; i++) sdsfree(tokens[i]);\n        s_free(tokens);\n        *count = 0;\n        return NULL;\n    }\n}\n\n/* Free the result returned by sdssplitlen(), or do nothing if 'tokens' is NULL. */\nvoid sdsfreesplitres(sds *tokens, int count) {\n    if (!tokens) return;\n    while(count--)\n        sdsfree(tokens[count]);\n    s_free(tokens);\n}\n\n/* Append to the sds string \"s\" an escaped string representation where\n * all the non-printable characters (tested with isprint()) are turned into\n * escapes in the form \"\\n\\r\\a....\" or \"\\x<hex-number>\".\n *\n * After the call, the modified sds string is no longer valid and all the\n * references must be substituted with the new pointer returned by the call. */\nsds sdscatrepr(sds s, const char *p, size_t len) {\n    s = sdscatlen(s,\"\\\"\",1);\n    while(len--) {\n        switch(*p) {\n        case '\\\\':\n        case '\"':\n            s = sdscatprintf(s,\"\\\\%c\",*p);\n            break;\n        case '\\n': s = sdscatlen(s,\"\\\\n\",2); break;\n        case '\\r': s = sdscatlen(s,\"\\\\r\",2); break;\n        case '\\t': s = sdscatlen(s,\"\\\\t\",2); break;\n        case '\\a': s = sdscatlen(s,\"\\\\a\",2); break;\n        case '\\b': s = sdscatlen(s,\"\\\\b\",2); break;\n        default:\n            if (isprint((int) *p))\n                s = sdscatprintf(s,\"%c\",*p);\n            else\n                s = sdscatprintf(s,\"\\\\x%02x\",(unsigned char)*p);\n            break;\n        }\n        p++;\n    }\n    return sdscatlen(s,\"\\\"\",1);\n}\n\n/* Helper function for sdssplitargs() that converts a hex digit into an\n * integer from 0 to 15 */\nint hex_digit_to_int(char c) {\n    switch(c) {\n    case '0': return 0;\n    case '1': return 1;\n    case '2': return 2;\n    case '3': return 3;\n    case '4': return 4;\n    case '5': return 5;\n    case '6': return 6;\n    case '7': return 7;\n    case '8': return 8;\n    case '9': return 9;\n    case 'a': case 'A': return 10;\n    case 'b': case 'B': return 11;\n    case 'c': case 'C': return 12;\n    case 'd': case 'D': return 13;\n    case 'e': case 'E': return 14;\n    case 'f': case 'F': return 15;\n    default: return 0;\n    }\n}\n\n/* Split a line into arguments, where every argument can be in the\n * following programming-language REPL-alike form:\n *\n * foo bar \"newline are supported\\n\" and \"\\xff\\x00otherstuff\"\n *\n * The number of arguments is stored into *argc, and an array\n * of sds is returned.\n *\n * The caller should free the resulting array of sds strings with\n * sdsfreesplitres().\n *\n * Note that sdscatrepr() is able to convert back a string into\n * a quoted string in the same format sdssplitargs() is able to parse.\n *\n * The function returns the allocated tokens on success, even when the\n * input string is empty, or NULL if the input contains unbalanced\n * quotes or closed quotes followed by non space characters\n * as in: \"foo\"bar or \"foo'\n */\nsds *sdssplitargs(const char *line, int *argc) {\n    const char *p = line;\n    char *current = NULL;\n    char **vector = NULL;\n\n    *argc = 0;\n    while(1) {\n        /* skip blanks */\n        while(*p && isspace((int) *p)) p++;\n        if (*p) {\n            /* get a token */\n            int inq=0;  /* set to 1 if we are in \"quotes\" */\n            int insq=0; /* set to 1 if we are in 'single quotes' */\n            int done=0;\n\n            if (current == NULL) current = sdsempty();\n            while(!done) {\n                if (inq) {\n                    if (*p == '\\\\' && *(p+1) == 'x' &&\n                                             isxdigit((int) *(p+2)) &&\n                                             isxdigit((int) *(p+3)))\n                    {\n                        unsigned char byte;\n\n                        byte = (hex_digit_to_int(*(p+2))*16)+\n                                hex_digit_to_int(*(p+3));\n                        current = sdscatlen(current,(char*)&byte,1);\n                        p += 3;\n                    } else if (*p == '\\\\' && *(p+1)) {\n                        char c;\n\n                        p++;\n                        switch(*p) {\n                        case 'n': c = '\\n'; break;\n                        case 'r': c = '\\r'; break;\n                        case 't': c = '\\t'; break;\n                        case 'b': c = '\\b'; break;\n                        case 'a': c = '\\a'; break;\n                        default: c = *p; break;\n                        }\n                        current = sdscatlen(current,&c,1);\n                    } else if (*p == '\"') {\n                        /* closing quote must be followed by a space or\n                         * nothing at all. */\n                        if (*(p+1) && !isspace((int) *(p+1))) goto err;\n                        done=1;\n                    } else if (!*p) {\n                        /* unterminated quotes */\n                        goto err;\n                    } else {\n                        current = sdscatlen(current,p,1);\n                    }\n                } else if (insq) {\n                    if (*p == '\\\\' && *(p+1) == '\\'') {\n                        p++;\n                        current = sdscatlen(current,\"'\",1);\n                    } else if (*p == '\\'') {\n                        /* closing quote must be followed by a space or\n                         * nothing at all. */\n                        if (*(p+1) && !isspace((int) *(p+1))) goto err;\n                        done=1;\n                    } else if (!*p) {\n                        /* unterminated quotes */\n                        goto err;\n                    } else {\n                        current = sdscatlen(current,p,1);\n                    }\n                } else {\n                    switch(*p) {\n                    case ' ':\n                    case '\\n':\n                    case '\\r':\n                    case '\\t':\n                    case '\\0':\n                        done=1;\n                        break;\n                    case '\"':\n                        inq=1;\n                        break;\n                    case '\\'':\n                        insq=1;\n                        break;\n                    default:\n                        current = sdscatlen(current,p,1);\n                        break;\n                    }\n                }\n                if (*p) p++;\n            }\n            /* add the token to the vector */\n            {\n                char **new_vector = s_realloc(vector,((*argc)+1)*sizeof(char*));\n                if (new_vector == NULL) {\n                    s_free(vector);\n                    return NULL;\n                }\n\n                vector = new_vector;\n                vector[*argc] = current;\n                (*argc)++;\n                current = NULL;\n            }\n        } else {\n            /* Even on empty input string return something not NULL. */\n            if (vector == NULL) vector = s_malloc(sizeof(void*));\n            return vector;\n        }\n    }\n\nerr:\n    while((*argc)--)\n        sdsfree(vector[*argc]);\n    s_free(vector);\n    if (current) sdsfree(current);\n    *argc = 0;\n    return NULL;\n}\n\n/* Modify the string substituting all the occurrences of the set of\n * characters specified in the 'from' string to the corresponding character\n * in the 'to' array.\n *\n * For instance: sdsmapchars(mystring, \"ho\", \"01\", 2)\n * will have the effect of turning the string \"hello\" into \"0ell1\".\n *\n * The function returns the sds string pointer, that is always the same\n * as the input pointer since no resize is needed. */\nsds sdsmapchars(sds s, const char *from, const char *to, size_t setlen) {\n    size_t j, i, l = sdslen(s);\n\n    for (j = 0; j < l; j++) {\n        for (i = 0; i < setlen; i++) {\n            if (s[j] == from[i]) {\n                s[j] = to[i];\n                break;\n            }\n        }\n    }\n    return s;\n}\n\n/* Join an array of C strings using the specified separator (also a C string).\n * Returns the result as an sds string. */\nsds sdsjoin(char **argv, int argc, char *sep) {\n    sds join = sdsempty();\n    int j;\n\n    for (j = 0; j < argc; j++) {\n        join = sdscat(join, argv[j]);\n        if (j != argc-1) join = sdscat(join,sep);\n    }\n    return join;\n}\n\n/* Like sdsjoin, but joins an array of SDS strings. */\nsds sdsjoinsds(sds *argv, int argc, const char *sep, size_t seplen) {\n    sds join = sdsempty();\n    int j;\n\n    for (j = 0; j < argc; j++) {\n        join = sdscatsds(join, argv[j]);\n        if (j != argc-1) join = sdscatlen(join,sep,seplen);\n    }\n    return join;\n}\n\n/* Wrappers to the allocators used by SDS. Note that SDS will actually\n * just use the macros defined into sdsalloc.h in order to avoid to pay\n * the overhead of function calls. Here we define these wrappers only for\n * the programs SDS is linked to, if they want to touch the SDS internals\n * even if they use a different allocator. */\nvoid *sds_malloc(size_t size) { return s_malloc(size); }\nvoid *sds_realloc(void *ptr, size_t size) { return s_realloc(ptr,size); }\nvoid sds_free(void *ptr) { s_free(ptr); }\n\n#if defined(SDS_TEST_MAIN)\n#include <stdio.h>\n#include \"testhelp.h\"\n#include \"limits.h\"\n\n#define UNUSED(x) (void)(x)\nint sdsTest(void) {\n    {\n        sds x = sdsnew(\"foo\"), y;\n\n        test_cond(\"Create a string and obtain the length\",\n            sdslen(x) == 3 && memcmp(x,\"foo\\0\",4) == 0)\n\n        sdsfree(x);\n        x = sdsnewlen(\"foo\",2);\n        test_cond(\"Create a string with specified length\",\n            sdslen(x) == 2 && memcmp(x,\"fo\\0\",3) == 0)\n\n        x = sdscat(x,\"bar\");\n        test_cond(\"Strings concatenation\",\n            sdslen(x) == 5 && memcmp(x,\"fobar\\0\",6) == 0);\n\n        x = sdscpy(x,\"a\");\n        test_cond(\"sdscpy() against an originally longer string\",\n            sdslen(x) == 1 && memcmp(x,\"a\\0\",2) == 0)\n\n        x = sdscpy(x,\"xyzxxxxxxxxxxyyyyyyyyyykkkkkkkkkk\");\n        test_cond(\"sdscpy() against an originally shorter string\",\n            sdslen(x) == 33 &&\n            memcmp(x,\"xyzxxxxxxxxxxyyyyyyyyyykkkkkkkkkk\\0\",33) == 0)\n\n        sdsfree(x);\n        x = sdscatprintf(sdsempty(),\"%d\",123);\n        test_cond(\"sdscatprintf() seems working in the base case\",\n            sdslen(x) == 3 && memcmp(x,\"123\\0\",4) == 0)\n\n        sdsfree(x);\n        x = sdsnew(\"--\");\n        x = sdscatfmt(x, \"Hello %s World %I,%I--\", \"Hi!\", LLONG_MIN,LLONG_MAX);\n        test_cond(\"sdscatfmt() seems working in the base case\",\n            sdslen(x) == 60 &&\n            memcmp(x,\"--Hello Hi! World -9223372036854775808,\"\n                     \"9223372036854775807--\",60) == 0)\n        printf(\"[%s]\\n\",x);\n\n        sdsfree(x);\n        x = sdsnew(\"--\");\n        x = sdscatfmt(x, \"%u,%U--\", UINT_MAX, ULLONG_MAX);\n        test_cond(\"sdscatfmt() seems working with unsigned numbers\",\n            sdslen(x) == 35 &&\n            memcmp(x,\"--4294967295,18446744073709551615--\",35) == 0)\n\n        sdsfree(x);\n        x = sdsnew(\" x \");\n        sdstrim(x,\" x\");\n        test_cond(\"sdstrim() works when all chars match\",\n            sdslen(x) == 0)\n\n        sdsfree(x);\n        x = sdsnew(\" x \");\n        sdstrim(x,\" \");\n        test_cond(\"sdstrim() works when a single char remains\",\n            sdslen(x) == 1 && x[0] == 'x')\n\n        sdsfree(x);\n        x = sdsnew(\"xxciaoyyy\");\n        sdstrim(x,\"xy\");\n        test_cond(\"sdstrim() correctly trims characters\",\n            sdslen(x) == 4 && memcmp(x,\"ciao\\0\",5) == 0)\n\n        y = sdsdup(x);\n        sdsrange(y,1,1);\n        test_cond(\"sdsrange(...,1,1)\",\n            sdslen(y) == 1 && memcmp(y,\"i\\0\",2) == 0)\n\n        sdsfree(y);\n        y = sdsdup(x);\n        sdsrange(y,1,-1);\n        test_cond(\"sdsrange(...,1,-1)\",\n            sdslen(y) == 3 && memcmp(y,\"iao\\0\",4) == 0)\n\n        sdsfree(y);\n        y = sdsdup(x);\n        sdsrange(y,-2,-1);\n        test_cond(\"sdsrange(...,-2,-1)\",\n            sdslen(y) == 2 && memcmp(y,\"ao\\0\",3) == 0)\n\n        sdsfree(y);\n        y = sdsdup(x);\n        sdsrange(y,2,1);\n        test_cond(\"sdsrange(...,2,1)\",\n            sdslen(y) == 0 && memcmp(y,\"\\0\",1) == 0)\n\n        sdsfree(y);\n        y = sdsdup(x);\n        sdsrange(y,1,100);\n        test_cond(\"sdsrange(...,1,100)\",\n            sdslen(y) == 3 && memcmp(y,\"iao\\0\",4) == 0)\n\n        sdsfree(y);\n        y = sdsdup(x);\n        sdsrange(y,100,100);\n        test_cond(\"sdsrange(...,100,100)\",\n            sdslen(y) == 0 && memcmp(y,\"\\0\",1) == 0)\n\n        sdsfree(y);\n        sdsfree(x);\n        x = sdsnew(\"foo\");\n        y = sdsnew(\"foa\");\n        test_cond(\"sdscmp(foo,foa)\", sdscmp(x,y) > 0)\n\n        sdsfree(y);\n        sdsfree(x);\n        x = sdsnew(\"bar\");\n        y = sdsnew(\"bar\");\n        test_cond(\"sdscmp(bar,bar)\", sdscmp(x,y) == 0)\n\n        sdsfree(y);\n        sdsfree(x);\n        x = sdsnew(\"aar\");\n        y = sdsnew(\"bar\");\n        test_cond(\"sdscmp(bar,bar)\", sdscmp(x,y) < 0)\n\n        sdsfree(y);\n        sdsfree(x);\n        x = sdsnewlen(\"\\a\\n\\0foo\\r\",7);\n        y = sdscatrepr(sdsempty(),x,sdslen(x));\n        test_cond(\"sdscatrepr(...data...)\",\n            memcmp(y,\"\\\"\\\\a\\\\n\\\\x00foo\\\\r\\\"\",15) == 0)\n\n        {\n            unsigned int oldfree;\n            char *p;\n            int step = 10, j, i;\n\n            sdsfree(x);\n            sdsfree(y);\n            x = sdsnew(\"0\");\n            test_cond(\"sdsnew() free/len buffers\", sdslen(x) == 1 && sdsavail(x) == 0);\n\n            /* Run the test a few times in order to hit the first two\n             * SDS header types. */\n            for (i = 0; i < 10; i++) {\n                int oldlen = sdslen(x);\n                x = sdsMakeRoomFor(x,step);\n                int type = x[-1]&SDS_TYPE_MASK;\n\n                test_cond(\"sdsMakeRoomFor() len\", sdslen(x) == oldlen);\n                if (type != SDS_TYPE_5) {\n                    test_cond(\"sdsMakeRoomFor() free\", sdsavail(x) >= step);\n                    oldfree = sdsavail(x);\n                }\n                p = x+oldlen;\n                for (j = 0; j < step; j++) {\n                    p[j] = 'A'+j;\n                }\n                sdsIncrLen(x,step);\n            }\n            test_cond(\"sdsMakeRoomFor() content\",\n                memcmp(\"0ABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJ\",x,101) == 0);\n            test_cond(\"sdsMakeRoomFor() final length\",sdslen(x)==101);\n\n            sdsfree(x);\n        }\n    }\n    test_report()\n    return 0;\n}\n#endif\n\n#ifdef SDS_TEST_MAIN\nint main(void) {\n    return sdsTest();\n}\n#endif\n"
  },
  {
    "path": "sds.h",
    "content": "/* SDSLib 2.0 -- A C dynamic strings library\n *\n * Copyright (c) 2006-2015, Salvatore Sanfilippo <antirez at gmail dot com>\n * Copyright (c) 2015, Oran Agra\n * Copyright (c) 2015, Redis Labs, Inc\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n *   * Redistributions of source code must retain the above copyright notice,\n *     this list of conditions and the following disclaimer.\n *   * Redistributions in binary form must reproduce the above copyright\n *     notice, this list of conditions and the following disclaimer in the\n *     documentation and/or other materials provided with the distribution.\n *   * Neither the name of Redis nor the names of its contributors may be used\n *     to endorse or promote products derived from this software without\n *     specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE\n * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n */\n\n#ifndef __SDS_H\n#define __SDS_H\n\n#define SDS_MAX_PREALLOC (1024*1024)\n#ifdef _MSC_VER\ntypedef intptr_t ssize_t;\n#define SSIZE_MAX INTPTR_MAX\n#ifndef __clang__\n#define __attribute__(x)\n#endif\n#endif\n\n#include <sys/types.h>\n#include <stdarg.h>\n#include <stdint.h>\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\ntypedef char *sds;\n\n/* Note: sdshdr5 is never used, we just access the flags byte directly.\n * However is here to document the layout of type 5 SDS strings. */\nstruct __attribute__ ((__packed__)) sdshdr5 {\n    unsigned char flags; /* 3 lsb of type, and 5 msb of string length */\n    char buf[];\n};\nstruct __attribute__ ((__packed__)) sdshdr8 {\n    uint8_t len; /* used */\n    uint8_t alloc; /* excluding the header and null terminator */\n    unsigned char flags; /* 3 lsb of type, 5 unused bits */\n    char buf[];\n};\nstruct __attribute__ ((__packed__)) sdshdr16 {\n    uint16_t len; /* used */\n    uint16_t alloc; /* excluding the header and null terminator */\n    unsigned char flags; /* 3 lsb of type, 5 unused bits */\n    char buf[];\n};\nstruct __attribute__ ((__packed__)) sdshdr32 {\n    uint32_t len; /* used */\n    uint32_t alloc; /* excluding the header and null terminator */\n    unsigned char flags; /* 3 lsb of type, 5 unused bits */\n    char buf[];\n};\nstruct __attribute__ ((__packed__)) sdshdr64 {\n    uint64_t len; /* used */\n    uint64_t alloc; /* excluding the header and null terminator */\n    unsigned char flags; /* 3 lsb of type, 5 unused bits */\n    char buf[];\n};\n\n#define SDS_TYPE_5  0\n#define SDS_TYPE_8  1\n#define SDS_TYPE_16 2\n#define SDS_TYPE_32 3\n#define SDS_TYPE_64 4\n#define SDS_TYPE_MASK 7\n#define SDS_TYPE_BITS 3\n#define SDS_HDR_VAR(T,s) struct sdshdr##T *sh = (struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T)));\n#define SDS_HDR(T,s) ((struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T))))\n#define SDS_TYPE_5_LEN(f) ((f)>>SDS_TYPE_BITS)\n\nstatic inline size_t sdslen(const sds s) {\n    unsigned char flags = s[-1];\n    switch(flags&SDS_TYPE_MASK) {\n        case SDS_TYPE_5:\n            return SDS_TYPE_5_LEN(flags);\n        case SDS_TYPE_8:\n            return SDS_HDR(8,s)->len;\n        case SDS_TYPE_16:\n            return SDS_HDR(16,s)->len;\n        case SDS_TYPE_32:\n            return SDS_HDR(32,s)->len;\n        case SDS_TYPE_64:\n            return SDS_HDR(64,s)->len;\n    }\n    return 0;\n}\n\nstatic inline size_t sdsavail(const sds s) {\n    unsigned char flags = s[-1];\n    switch(flags&SDS_TYPE_MASK) {\n        case SDS_TYPE_5: {\n            return 0;\n        }\n        case SDS_TYPE_8: {\n            SDS_HDR_VAR(8,s);\n            return sh->alloc - sh->len;\n        }\n        case SDS_TYPE_16: {\n            SDS_HDR_VAR(16,s);\n            return sh->alloc - sh->len;\n        }\n        case SDS_TYPE_32: {\n            SDS_HDR_VAR(32,s);\n            return sh->alloc - sh->len;\n        }\n        case SDS_TYPE_64: {\n            SDS_HDR_VAR(64,s);\n            return sh->alloc - sh->len;\n        }\n    }\n    return 0;\n}\n\nstatic inline void sdssetlen(sds s, size_t newlen) {\n    unsigned char flags = s[-1];\n    switch(flags&SDS_TYPE_MASK) {\n        case SDS_TYPE_5:\n            {\n                unsigned char *fp = ((unsigned char*)s)-1;\n                *fp = (unsigned char)(SDS_TYPE_5 | (newlen << SDS_TYPE_BITS));\n            }\n            break;\n        case SDS_TYPE_8:\n            SDS_HDR(8,s)->len = (uint8_t)newlen;\n            break;\n        case SDS_TYPE_16:\n            SDS_HDR(16,s)->len = (uint16_t)newlen;\n            break;\n        case SDS_TYPE_32:\n            SDS_HDR(32,s)->len = (uint32_t)newlen;\n            break;\n        case SDS_TYPE_64:\n            SDS_HDR(64,s)->len = (uint64_t)newlen;\n            break;\n    }\n}\n\nstatic inline void sdsinclen(sds s, size_t inc) {\n    unsigned char flags = s[-1];\n    switch(flags&SDS_TYPE_MASK) {\n        case SDS_TYPE_5:\n            {\n                unsigned char *fp = ((unsigned char*)s)-1;\n                unsigned char newlen = SDS_TYPE_5_LEN(flags)+(unsigned char)inc;\n                *fp = SDS_TYPE_5 | (newlen << SDS_TYPE_BITS);\n            }\n            break;\n        case SDS_TYPE_8:\n            SDS_HDR(8,s)->len += (uint8_t)inc;\n            break;\n        case SDS_TYPE_16:\n            SDS_HDR(16,s)->len += (uint16_t)inc;\n            break;\n        case SDS_TYPE_32:\n            SDS_HDR(32,s)->len += (uint32_t)inc;\n            break;\n        case SDS_TYPE_64:\n            SDS_HDR(64,s)->len += (uint64_t)inc;\n            break;\n    }\n}\n\n/* sdsalloc() = sdsavail() + sdslen() */\nstatic inline size_t sdsalloc(const sds s) {\n    unsigned char flags = s[-1];\n    switch(flags&SDS_TYPE_MASK) {\n        case SDS_TYPE_5:\n            return SDS_TYPE_5_LEN(flags);\n        case SDS_TYPE_8:\n            return SDS_HDR(8,s)->alloc;\n        case SDS_TYPE_16:\n            return SDS_HDR(16,s)->alloc;\n        case SDS_TYPE_32:\n            return SDS_HDR(32,s)->alloc;\n        case SDS_TYPE_64:\n            return SDS_HDR(64,s)->alloc;\n    }\n    return 0;\n}\n\nstatic inline void sdssetalloc(sds s, size_t newlen) {\n    unsigned char flags = s[-1];\n    switch(flags&SDS_TYPE_MASK) {\n        case SDS_TYPE_5:\n            /* Nothing to do, this type has no total allocation info. */\n            break;\n        case SDS_TYPE_8:\n            SDS_HDR(8,s)->alloc = (uint8_t)newlen;\n            break;\n        case SDS_TYPE_16:\n            SDS_HDR(16,s)->alloc = (uint16_t)newlen;\n            break;\n        case SDS_TYPE_32:\n            SDS_HDR(32,s)->alloc = (uint32_t)newlen;\n            break;\n        case SDS_TYPE_64:\n            SDS_HDR(64,s)->alloc = (uint64_t)newlen;\n            break;\n    }\n}\n\nsds sdsnewlen(const void *init, size_t initlen);\nsds sdsnew(const char *init);\nsds sdsempty(void);\nsds sdsdup(const sds s);\nvoid sdsfree(sds s);\nsds sdsgrowzero(sds s, size_t len);\nsds sdscatlen(sds s, const void *t, size_t len);\nsds sdscat(sds s, const char *t);\nsds sdscatsds(sds s, const sds t);\nsds sdscpylen(sds s, const char *t, size_t len);\nsds sdscpy(sds s, const char *t);\n\nsds sdscatvprintf(sds s, const char *fmt, va_list ap);\n#ifdef __GNUC__\nsds sdscatprintf(sds s, const char *fmt, ...)\n    __attribute__((format(printf, 2, 3)));\n#else\nsds sdscatprintf(sds s, const char *fmt, ...);\n#endif\n\nsds sdscatfmt(sds s, char const *fmt, ...);\nsds sdstrim(sds s, const char *cset);\nint sdsrange(sds s, ssize_t start, ssize_t end);\nvoid sdsupdatelen(sds s);\nvoid sdsclear(sds s);\nint sdscmp(const sds s1, const sds s2);\nsds *sdssplitlen(const char *s, int len, const char *sep, int seplen, int *count);\nvoid sdsfreesplitres(sds *tokens, int count);\nvoid sdstolower(sds s);\nvoid sdstoupper(sds s);\nsds sdsfromlonglong(long long value);\nsds sdscatrepr(sds s, const char *p, size_t len);\nsds *sdssplitargs(const char *line, int *argc);\nsds sdsmapchars(sds s, const char *from, const char *to, size_t setlen);\nsds sdsjoin(char **argv, int argc, char *sep);\nsds sdsjoinsds(sds *argv, int argc, const char *sep, size_t seplen);\n\n/* Low level functions exposed to the user API */\nsds sdsMakeRoomFor(sds s, size_t addlen);\nvoid sdsIncrLen(sds s, int incr);\nsds sdsRemoveFreeSpace(sds s);\nsize_t sdsAllocSize(sds s);\nvoid *sdsAllocPtr(sds s);\n\n/* Export the allocator used by SDS to the program using SDS.\n * Sometimes the program SDS is linked to, may use a different set of\n * allocators, but may want to allocate or free things that SDS will\n * respectively free or allocate. */\nvoid *sds_malloc(size_t size);\nvoid *sds_realloc(void *ptr, size_t size);\nvoid sds_free(void *ptr);\n\n#ifdef REDIS_TEST\nint sdsTest(int argc, char *argv[]);\n#endif\n\n#ifdef __cplusplus\n}\n#endif\n#endif\n"
  },
  {
    "path": "sdsalloc.h",
    "content": "/* SDSLib 2.0 -- A C dynamic strings library\n *\n * Copyright (c) 2006-2015, Salvatore Sanfilippo <antirez at gmail dot com>\n * Copyright (c) 2015, Oran Agra\n * Copyright (c) 2015, Redis Labs, Inc\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n *   * Redistributions of source code must retain the above copyright notice,\n *     this list of conditions and the following disclaimer.\n *   * Redistributions in binary form must reproduce the above copyright\n *     notice, this list of conditions and the following disclaimer in the\n *     documentation and/or other materials provided with the distribution.\n *   * Neither the name of Redis nor the names of its contributors may be used\n *     to endorse or promote products derived from this software without\n *     specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE\n * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n */\n\n/* SDS allocator selection.\n *\n * This file is used in order to change the SDS allocator at compile time.\n * Just define the following defines to what you want to use. Also add\n * the include of your alternate allocator if needed (not needed in order\n * to use the default libc allocator). */\n\n#include \"alloc.h\"\n\n#define s_malloc hi_malloc\n#define s_realloc hi_realloc\n#define s_free hi_free\n"
  },
  {
    "path": "sockcompat.c",
    "content": "/*\n * Copyright (c) 2019, Marcus Geelnard <m at bitsnbites dot eu>\n *\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n *   * Redistributions of source code must retain the above copyright notice,\n *     this list of conditions and the following disclaimer.\n *   * Redistributions in binary form must reproduce the above copyright\n *     notice, this list of conditions and the following disclaimer in the\n *     documentation and/or other materials provided with the distribution.\n *   * Neither the name of Redis nor the names of its contributors may be used\n *     to endorse or promote products derived from this software without\n *     specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE\n * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n */\n\n#define REDIS_SOCKCOMPAT_IMPLEMENTATION\n#include \"sockcompat.h\"\n\n#ifdef _WIN32\nstatic int _wsaErrorToErrno(int err) {\n    switch (err) {\n        case WSAEWOULDBLOCK:\n            return EWOULDBLOCK;\n        case WSAEINPROGRESS:\n            return EINPROGRESS;\n        case WSAEALREADY:\n            return EALREADY;\n        case WSAENOTSOCK:\n            return ENOTSOCK;\n        case WSAEDESTADDRREQ:\n            return EDESTADDRREQ;\n        case WSAEMSGSIZE:\n            return EMSGSIZE;\n        case WSAEPROTOTYPE:\n            return EPROTOTYPE;\n        case WSAENOPROTOOPT:\n            return ENOPROTOOPT;\n        case WSAEPROTONOSUPPORT:\n            return EPROTONOSUPPORT;\n        case WSAEOPNOTSUPP:\n            return EOPNOTSUPP;\n        case WSAEAFNOSUPPORT:\n            return EAFNOSUPPORT;\n        case WSAEADDRINUSE:\n            return EADDRINUSE;\n        case WSAEADDRNOTAVAIL:\n            return EADDRNOTAVAIL;\n        case WSAENETDOWN:\n            return ENETDOWN;\n        case WSAENETUNREACH:\n            return ENETUNREACH;\n        case WSAENETRESET:\n            return ENETRESET;\n        case WSAECONNABORTED:\n            return ECONNABORTED;\n        case WSAECONNRESET:\n            return ECONNRESET;\n        case WSAENOBUFS:\n            return ENOBUFS;\n        case WSAEISCONN:\n            return EISCONN;\n        case WSAENOTCONN:\n            return ENOTCONN;\n        case WSAETIMEDOUT:\n            return ETIMEDOUT;\n        case WSAECONNREFUSED:\n            return ECONNREFUSED;\n        case WSAELOOP:\n            return ELOOP;\n        case WSAENAMETOOLONG:\n            return ENAMETOOLONG;\n        case WSAEHOSTUNREACH:\n            return EHOSTUNREACH;\n        case WSAENOTEMPTY:\n            return ENOTEMPTY;\n        default:\n            /* We just return a generic I/O error if we could not find a relevant error. */\n            return EIO;\n    }\n}\n\nstatic void _updateErrno(int success) {\n    errno = success ? 0 : _wsaErrorToErrno(WSAGetLastError());\n}\n\nstatic int _initWinsock() {\n    static int s_initialized = 0;\n    if (!s_initialized) {\n        static WSADATA wsadata;\n        int err = WSAStartup(MAKEWORD(2,2), &wsadata);\n        if (err != 0) {\n            errno = _wsaErrorToErrno(err);\n            return 0;\n        }\n        s_initialized = 1;\n    }\n    return 1;\n}\n\nint win32_getaddrinfo(const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res) {\n    /* Note: This function is likely to be called before other functions, so run init here. */\n    if (!_initWinsock()) {\n        return EAI_FAIL;\n    }\n\n    switch (getaddrinfo(node, service, hints, res)) {\n        case 0:                     return 0;\n        case WSATRY_AGAIN:          return EAI_AGAIN;\n        case WSAEINVAL:             return EAI_BADFLAGS;\n        case WSAEAFNOSUPPORT:       return EAI_FAMILY;\n        case WSA_NOT_ENOUGH_MEMORY: return EAI_MEMORY;\n        case WSAHOST_NOT_FOUND:     return EAI_NONAME;\n        case WSATYPE_NOT_FOUND:     return EAI_SERVICE;\n        case WSAESOCKTNOSUPPORT:    return EAI_SOCKTYPE;\n        default:                    return EAI_FAIL;     /* Including WSANO_RECOVERY */\n    }\n}\n\nconst char *win32_gai_strerror(int errcode) {\n    switch (errcode) {\n        case 0:            errcode = 0;                     break;\n        case EAI_AGAIN:    errcode = WSATRY_AGAIN;          break;\n        case EAI_BADFLAGS: errcode = WSAEINVAL;             break;\n        case EAI_FAMILY:   errcode = WSAEAFNOSUPPORT;       break;\n        case EAI_MEMORY:   errcode = WSA_NOT_ENOUGH_MEMORY; break;\n        case EAI_NONAME:   errcode = WSAHOST_NOT_FOUND;     break;\n        case EAI_SERVICE:  errcode = WSATYPE_NOT_FOUND;     break;\n        case EAI_SOCKTYPE: errcode = WSAESOCKTNOSUPPORT;    break;\n        default:           errcode = WSANO_RECOVERY;        break; /* Including EAI_FAIL */\n    }\n    return gai_strerror(errcode);\n}\n\nvoid win32_freeaddrinfo(struct addrinfo *res) {\n    freeaddrinfo(res);\n}\n\nSOCKET win32_socket(int domain, int type, int protocol) {\n    SOCKET s;\n\n    /* Note: This function is likely to be called before other functions, so run init here. */\n    if (!_initWinsock()) {\n        return INVALID_SOCKET;\n    }\n\n    _updateErrno((s = socket(domain, type, protocol)) != INVALID_SOCKET);\n    return s;\n}\n\nint win32_ioctl(SOCKET fd, unsigned long request, unsigned long *argp) {\n    int ret = ioctlsocket(fd, (long)request, argp);\n    _updateErrno(ret != SOCKET_ERROR);\n    return ret != SOCKET_ERROR ? ret : -1;\n}\n\nint win32_bind(SOCKET sockfd, const struct sockaddr *addr, socklen_t addrlen) {\n    int ret = bind(sockfd, addr, addrlen);\n    _updateErrno(ret != SOCKET_ERROR);\n    return ret != SOCKET_ERROR ? ret : -1;\n}\n\nint win32_connect(SOCKET sockfd, const struct sockaddr *addr, socklen_t addrlen) {\n    int ret = connect(sockfd, addr, addrlen);\n    _updateErrno(ret != SOCKET_ERROR);\n\n    /* For Winsock connect(), the WSAEWOULDBLOCK error means the same thing as\n     * EINPROGRESS for POSIX connect(), so we do that translation to keep POSIX\n     * logic consistent.\n     * Additionally, WSAALREADY is can be reported as WSAEINVAL to  and this is\n     * translated to EIO.  Convert appropriately\n     */\n    int err = errno;\n    if (err == EWOULDBLOCK) {\n        errno = EINPROGRESS;\n    }\n    else if (err == EIO) {\n        errno = EALREADY;\n    }\n\n    return ret != SOCKET_ERROR ? ret : -1;\n}\n\nint win32_getsockopt(SOCKET sockfd, int level, int optname, void *optval, socklen_t *optlen) {\n    int ret = 0;\n    if ((level == SOL_SOCKET) && ((optname == SO_RCVTIMEO) || (optname == SO_SNDTIMEO))) {\n        if (*optlen >= sizeof (struct timeval)) {\n            struct timeval *tv = optval;\n            DWORD timeout = 0;\n            socklen_t dwlen = 0;\n            ret = getsockopt(sockfd, level, optname, (char *)&timeout, &dwlen);\n            tv->tv_sec = timeout / 1000;\n            tv->tv_usec = (timeout * 1000) % 1000000;\n        } else {\n            ret = WSAEFAULT;\n        }\n        *optlen = sizeof (struct timeval);\n    } else {\n        ret = getsockopt(sockfd, level, optname, (char*)optval, optlen);\n    }\n    if (ret != SOCKET_ERROR && level == SOL_SOCKET && optname == SO_ERROR) {\n        /* translate SO_ERROR codes, if non-zero */\n        int err = *(int*)optval;\n        if (err != 0) {\n            err = _wsaErrorToErrno(err);\n            *(int*)optval = err;\n        }\n    }\n    _updateErrno(ret != SOCKET_ERROR);\n    return ret != SOCKET_ERROR ? ret : -1;\n}\n\nint win32_setsockopt(SOCKET sockfd, int level, int optname, const void *optval, socklen_t optlen) {\n    int ret = 0;\n    if ((level == SOL_SOCKET) && ((optname == SO_RCVTIMEO) || (optname == SO_SNDTIMEO))) {\n        const struct timeval *tv = optval;\n        DWORD timeout = tv->tv_sec * 1000 + tv->tv_usec / 1000;\n        ret = setsockopt(sockfd, level, optname, (const char*)&timeout, sizeof(DWORD));\n    } else {\n        ret = setsockopt(sockfd, level, optname, (const char*)optval, optlen);\n    }\n    _updateErrno(ret != SOCKET_ERROR);\n    return ret != SOCKET_ERROR ? ret : -1;\n}\n\nint win32_close(SOCKET fd) {\n    int ret = closesocket(fd);\n    _updateErrno(ret != SOCKET_ERROR);\n    return ret != SOCKET_ERROR ? ret : -1;\n}\n\nssize_t win32_recv(SOCKET sockfd, void *buf, size_t len, int flags) {\n    int ret = recv(sockfd, (char*)buf, (int)len, flags);\n    _updateErrno(ret != SOCKET_ERROR);\n    return ret != SOCKET_ERROR ? ret : -1;\n}\n\nssize_t win32_send(SOCKET sockfd, const void *buf, size_t len, int flags) {\n    int ret = send(sockfd, (const char*)buf, (int)len, flags);\n    _updateErrno(ret != SOCKET_ERROR);\n    return ret != SOCKET_ERROR ? ret : -1;\n}\n\nint win32_poll(struct pollfd *fds, nfds_t nfds, int timeout) {\n    int ret = WSAPoll(fds, nfds, timeout);\n    _updateErrno(ret != SOCKET_ERROR);\n    return ret != SOCKET_ERROR ? ret : -1;\n}\n\nint win32_redisKeepAlive(SOCKET sockfd, int interval_ms) {\n    struct tcp_keepalive cfg;\n    DWORD bytes_in;\n    int res;\n\n    cfg.onoff = 1;\n    cfg.keepaliveinterval = interval_ms;\n    cfg.keepalivetime = interval_ms;\n\n    res = WSAIoctl(sockfd, SIO_KEEPALIVE_VALS, &cfg,\n                   sizeof(struct tcp_keepalive), NULL, 0,\n                   &bytes_in, NULL, NULL);\n\n    return res == 0 ? 0 : _wsaErrorToErrno(res);\n}\n\n#endif /* _WIN32 */\n"
  },
  {
    "path": "sockcompat.h",
    "content": "/*\n * Copyright (c) 2019, Marcus Geelnard <m at bitsnbites dot eu>\n *\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n *   * Redistributions of source code must retain the above copyright notice,\n *     this list of conditions and the following disclaimer.\n *   * Redistributions in binary form must reproduce the above copyright\n *     notice, this list of conditions and the following disclaimer in the\n *     documentation and/or other materials provided with the distribution.\n *   * Neither the name of Redis nor the names of its contributors may be used\n *     to endorse or promote products derived from this software without\n *     specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE\n * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n */\n\n#ifndef __SOCKCOMPAT_H\n#define __SOCKCOMPAT_H\n\n#ifndef _WIN32\n/* For POSIX systems we use the standard BSD socket API. */\n#include <unistd.h>\n#include <sys/socket.h>\n#include <sys/select.h>\n#include <sys/un.h>\n#include <netinet/in.h>\n#include <netinet/tcp.h>\n#include <arpa/inet.h>\n#include <netdb.h>\n#include <poll.h>\n#else\n/* For Windows we use winsock. */\n#undef _WIN32_WINNT\n#define _WIN32_WINNT 0x0600 /* To get WSAPoll etc. */\n#include <winsock2.h>\n#include <ws2tcpip.h>\n#include <stddef.h>\n#include <errno.h>\n#include <mstcpip.h>\n\n#ifdef _MSC_VER\ntypedef intptr_t ssize_t;\n#endif\n\n/* Emulate the parts of the BSD socket API that we need (override the winsock signatures). */\nint win32_getaddrinfo(const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res);\nconst char *win32_gai_strerror(int errcode);\nvoid win32_freeaddrinfo(struct addrinfo *res);\nSOCKET win32_socket(int domain, int type, int protocol);\nint win32_ioctl(SOCKET fd, unsigned long request, unsigned long *argp);\nint win32_bind(SOCKET sockfd, const struct sockaddr *addr, socklen_t addrlen);\nint win32_connect(SOCKET sockfd, const struct sockaddr *addr, socklen_t addrlen);\nint win32_getsockopt(SOCKET sockfd, int level, int optname, void *optval, socklen_t *optlen);\nint win32_setsockopt(SOCKET sockfd, int level, int optname, const void *optval, socklen_t optlen);\nint win32_close(SOCKET fd);\nssize_t win32_recv(SOCKET sockfd, void *buf, size_t len, int flags);\nssize_t win32_send(SOCKET sockfd, const void *buf, size_t len, int flags);\ntypedef ULONG nfds_t;\nint win32_poll(struct pollfd *fds, nfds_t nfds, int timeout);\n\nint win32_redisKeepAlive(SOCKET sockfd, int interval_ms);\n\n#ifndef REDIS_SOCKCOMPAT_IMPLEMENTATION\n#define getaddrinfo(node, service, hints, res) win32_getaddrinfo(node, service, hints, res)\n#undef gai_strerror\n#define gai_strerror(errcode) win32_gai_strerror(errcode)\n#define freeaddrinfo(res) win32_freeaddrinfo(res)\n#define socket(domain, type, protocol) win32_socket(domain, type, protocol)\n#define ioctl(fd, request, argp) win32_ioctl(fd, request, argp)\n#define bind(sockfd, addr, addrlen) win32_bind(sockfd, addr, addrlen)\n#define connect(sockfd, addr, addrlen) win32_connect(sockfd, addr, addrlen)\n#define getsockopt(sockfd, level, optname, optval, optlen) win32_getsockopt(sockfd, level, optname, optval, optlen)\n#define setsockopt(sockfd, level, optname, optval, optlen) win32_setsockopt(sockfd, level, optname, optval, optlen)\n#define close(fd) win32_close(fd)\n#define recv(sockfd, buf, len, flags) win32_recv(sockfd, buf, len, flags)\n#define send(sockfd, buf, len, flags) win32_send(sockfd, buf, len, flags)\n#define poll(fds, nfds, timeout) win32_poll(fds, nfds, timeout)\n#endif /* REDIS_SOCKCOMPAT_IMPLEMENTATION */\n#endif /* _WIN32 */\n\n#endif /* __SOCKCOMPAT_H */\n"
  },
  {
    "path": "ssl.c",
    "content": "/*\n * Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com>\n * Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com>\n * Copyright (c) 2019, Redis Labs\n *\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n *   * Redistributions of source code must retain the above copyright notice,\n *     this list of conditions and the following disclaimer.\n *   * Redistributions in binary form must reproduce the above copyright\n *     notice, this list of conditions and the following disclaimer in the\n *     documentation and/or other materials provided with the distribution.\n *   * Neither the name of Redis nor the names of its contributors may be used\n *     to endorse or promote products derived from this software without\n *     specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE\n * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n */\n\n#include \"hiredis.h\"\n#include \"async.h\"\n#include \"net.h\"\n\n#include <openssl/ssl.h>\n#include <openssl/err.h>\n\n#include <assert.h>\n#include <errno.h>\n#include <string.h>\n#ifdef _WIN32\n#include <windows.h>\n#include <wincrypt.h>\n#ifdef OPENSSL_IS_BORINGSSL\n#undef X509_NAME\n#undef X509_EXTENSIONS\n#undef PKCS7_ISSUER_AND_SERIAL\n#undef PKCS7_SIGNER_INFO\n#undef OCSP_REQUEST\n#undef OCSP_RESPONSE\n#endif\n#else\n#include <pthread.h>\n#endif\n\n#include \"win32.h\"\n#include \"async_private.h\"\n#include \"hiredis_ssl.h\"\n\n#define OPENSSL_1_1_0 0x10100000L\n\nvoid __redisSetError(redisContext *c, int type, const char *str);\n\nstruct redisSSLContext {\n    /* Associated OpenSSL SSL_CTX as created by redisCreateSSLContext() */\n    SSL_CTX *ssl_ctx;\n\n    /* Requested SNI, or NULL */\n    char *server_name;\n};\n\n/* The SSL connection context is attached to SSL/TLS connections as a privdata. */\ntypedef struct redisSSL {\n    /**\n     * OpenSSL SSL object.\n     */\n    SSL *ssl;\n\n    /**\n     * SSL_write() requires to be called again with the same arguments it was\n     * previously called with in the event of an SSL_read/SSL_write situation\n     */\n    size_t lastLen;\n\n    /** Whether the SSL layer requires read (possibly before a write) */\n    int wantRead;\n\n    /**\n     * Whether a write was requested prior to a read. If set, the write()\n     * should resume whenever a read takes place, if possible\n     */\n    int pendingWrite;\n} redisSSL;\n\n/* Forward declaration */\nredisContextFuncs redisContextSSLFuncs;\n\n/**\n * OpenSSL global initialization and locking handling callbacks.\n * Note that this is only required for OpenSSL < 1.1.0.\n */\n\n#if OPENSSL_VERSION_NUMBER < OPENSSL_1_1_0\n#define HIREDIS_USE_CRYPTO_LOCKS\n#endif\n\n#ifdef HIREDIS_USE_CRYPTO_LOCKS\n#ifdef _WIN32\ntypedef CRITICAL_SECTION sslLockType;\nstatic void sslLockInit(sslLockType* l) {\n    InitializeCriticalSection(l);\n}\nstatic void sslLockAcquire(sslLockType* l) {\n    EnterCriticalSection(l);\n}\nstatic void sslLockRelease(sslLockType* l) {\n    LeaveCriticalSection(l);\n}\n#else\ntypedef pthread_mutex_t sslLockType;\nstatic void sslLockInit(sslLockType *l) {\n    pthread_mutex_init(l, NULL);\n}\nstatic void sslLockAcquire(sslLockType *l) {\n    pthread_mutex_lock(l);\n}\nstatic void sslLockRelease(sslLockType *l) {\n    pthread_mutex_unlock(l);\n}\n#endif\n\nstatic sslLockType* ossl_locks;\n\nstatic void opensslDoLock(int mode, int lkid, const char *f, int line) {\n    sslLockType *l = ossl_locks + lkid;\n\n    if (mode & CRYPTO_LOCK) {\n        sslLockAcquire(l);\n    } else {\n        sslLockRelease(l);\n    }\n\n    (void)f;\n    (void)line;\n}\n\nstatic int initOpensslLocks(void) {\n    unsigned ii, nlocks;\n    if (CRYPTO_get_locking_callback() != NULL) {\n        /* Someone already set the callback before us. Don't destroy it! */\n        return REDIS_OK;\n    }\n    nlocks = CRYPTO_num_locks();\n    ossl_locks = hi_malloc(sizeof(*ossl_locks) * nlocks);\n    if (ossl_locks == NULL)\n        return REDIS_ERR;\n\n    for (ii = 0; ii < nlocks; ii++) {\n        sslLockInit(ossl_locks + ii);\n    }\n    CRYPTO_set_locking_callback(opensslDoLock);\n    return REDIS_OK;\n}\n#endif /* HIREDIS_USE_CRYPTO_LOCKS */\n\nint redisInitOpenSSL(void)\n{\n#ifdef HIREDIS_USE_CRYPTO_LOCKS\n    SSL_library_init();\n    initOpensslLocks();\n#endif\n\n    return REDIS_OK;\n}\n\n/**\n * redisSSLContext helper context destruction.\n */\n\nconst char *redisSSLContextGetError(redisSSLContextError error)\n{\n    switch (error) {\n        case REDIS_SSL_CTX_NONE:\n            return \"No Error\";\n        case REDIS_SSL_CTX_CREATE_FAILED:\n            return \"Failed to create OpenSSL SSL_CTX\";\n        case REDIS_SSL_CTX_CERT_KEY_REQUIRED:\n            return \"Client cert and key must both be specified or skipped\";\n        case REDIS_SSL_CTX_CA_CERT_LOAD_FAILED:\n            return \"Failed to load CA Certificate or CA Path\";\n        case REDIS_SSL_CTX_CLIENT_CERT_LOAD_FAILED:\n            return \"Failed to load client certificate\";\n        case REDIS_SSL_CTX_PRIVATE_KEY_LOAD_FAILED:\n            return \"Failed to load private key\";\n        case REDIS_SSL_CTX_OS_CERTSTORE_OPEN_FAILED:\n            return \"Failed to open system certificate store\";\n        case REDIS_SSL_CTX_OS_CERT_ADD_FAILED:\n            return \"Failed to add CA certificates obtained from system to the SSL context\";\n        default:\n            return \"Unknown error code\";\n    }\n}\n\nvoid redisFreeSSLContext(redisSSLContext *ctx)\n{\n    if (!ctx)\n        return;\n\n    if (ctx->server_name) {\n        hi_free(ctx->server_name);\n        ctx->server_name = NULL;\n    }\n\n    if (ctx->ssl_ctx) {\n        SSL_CTX_free(ctx->ssl_ctx);\n        ctx->ssl_ctx = NULL;\n    }\n\n    hi_free(ctx);\n}\n\n\n/**\n * redisSSLContext helper context initialization.\n */\n\nredisSSLContext *redisCreateSSLContext(const char *cacert_filename, const char *capath,\n        const char *cert_filename, const char *private_key_filename,\n        const char *server_name, redisSSLContextError *error)\n{\n    redisSSLOptions options = {\n        .cacert_filename = cacert_filename,\n        .capath = capath,\n        .cert_filename = cert_filename,\n        .private_key_filename = private_key_filename,\n        .server_name = server_name,\n        .verify_mode = REDIS_SSL_VERIFY_PEER,\n    };\n\n    return redisCreateSSLContextWithOptions(&options, error);\n}\n\nredisSSLContext *redisCreateSSLContextWithOptions(redisSSLOptions *options, redisSSLContextError *error) {\n    const char *cacert_filename = options->cacert_filename;\n    const char *capath = options->capath;\n    const char *cert_filename = options->cert_filename;\n    const char *private_key_filename = options->private_key_filename;\n    const char *server_name = options->server_name;\n\n#ifdef _WIN32\n    HCERTSTORE win_store = NULL;\n    PCCERT_CONTEXT win_ctx = NULL;\n#endif\n\n    redisSSLContext *ctx = hi_calloc(1, sizeof(redisSSLContext));\n    if (ctx == NULL)\n        goto error;\n\n    const SSL_METHOD *ssl_method;\n#if OPENSSL_VERSION_NUMBER >= OPENSSL_1_1_0\n    ssl_method = TLS_client_method();\n#else\n    ssl_method = SSLv23_client_method();\n#endif\n\n    ctx->ssl_ctx = SSL_CTX_new(ssl_method);\n    if (!ctx->ssl_ctx) {\n        if (error) *error = REDIS_SSL_CTX_CREATE_FAILED;\n        goto error;\n    }\n\n#if OPENSSL_VERSION_NUMBER >= OPENSSL_1_1_0\n    SSL_CTX_set_min_proto_version(ctx->ssl_ctx, TLS1_2_VERSION);\n#else\n    SSL_CTX_set_options(ctx->ssl_ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1);\n#endif\n\n    SSL_CTX_set_verify(ctx->ssl_ctx, options->verify_mode, NULL);\n\n    if ((cert_filename != NULL && private_key_filename == NULL) ||\n            (private_key_filename != NULL && cert_filename == NULL)) {\n        if (error) *error = REDIS_SSL_CTX_CERT_KEY_REQUIRED;\n        goto error;\n    }\n\n    if (capath || cacert_filename) {\n#ifdef _WIN32\n        if (0 == strcmp(cacert_filename, \"wincert\")) {\n            win_store = CertOpenSystemStore(NULL, \"Root\");\n            if (!win_store) {\n                if (error) *error = REDIS_SSL_CTX_OS_CERTSTORE_OPEN_FAILED;\n                goto error;\n            }\n            X509_STORE* store = SSL_CTX_get_cert_store(ctx->ssl_ctx);\n            while (win_ctx = CertEnumCertificatesInStore(win_store, win_ctx)) {\n                X509* x509 = NULL;\n                x509 = d2i_X509(NULL, (const unsigned char**)&win_ctx->pbCertEncoded, win_ctx->cbCertEncoded);\n                if (x509) {\n                    if ((1 != X509_STORE_add_cert(store, x509)) ||\n                        (1 != SSL_CTX_add_client_CA(ctx->ssl_ctx, x509)))\n                    {\n                        if (error) *error = REDIS_SSL_CTX_OS_CERT_ADD_FAILED;\n                        goto error;\n                    }\n                    X509_free(x509);\n                }\n            }\n            CertFreeCertificateContext(win_ctx);\n            CertCloseStore(win_store, 0);\n        } else\n#endif\n        if (!SSL_CTX_load_verify_locations(ctx->ssl_ctx, cacert_filename, capath)) {\n            if (error) *error = REDIS_SSL_CTX_CA_CERT_LOAD_FAILED;\n            goto error;\n        }\n    } else {\n        if (!SSL_CTX_set_default_verify_paths(ctx->ssl_ctx)) {\n            if (error) *error = REDIS_SSL_CTX_CLIENT_DEFAULT_CERT_FAILED;\n            goto error;\n        }\n    }\n\n    if (cert_filename) {\n        if (!SSL_CTX_use_certificate_chain_file(ctx->ssl_ctx, cert_filename)) {\n            if (error) *error = REDIS_SSL_CTX_CLIENT_CERT_LOAD_FAILED;\n            goto error;\n        }\n        if (!SSL_CTX_use_PrivateKey_file(ctx->ssl_ctx, private_key_filename, SSL_FILETYPE_PEM)) {\n            if (error) *error = REDIS_SSL_CTX_PRIVATE_KEY_LOAD_FAILED;\n            goto error;\n        }\n    }\n\n    if (server_name)\n        ctx->server_name = hi_strdup(server_name);\n\n    return ctx;\n\nerror:\n#ifdef _WIN32\n    CertFreeCertificateContext(win_ctx);\n    CertCloseStore(win_store, 0);\n#endif\n    redisFreeSSLContext(ctx);\n    return NULL;\n}\n\n/**\n * SSL Connection initialization.\n */\n\n\nstatic int redisSSLConnect(redisContext *c, SSL *ssl) {\n    if (c->privctx) {\n        __redisSetError(c, REDIS_ERR_OTHER, \"redisContext was already associated\");\n        return REDIS_ERR;\n    }\n\n    redisSSL *rssl = hi_calloc(1, sizeof(redisSSL));\n    if (rssl == NULL) {\n        __redisSetError(c, REDIS_ERR_OOM, \"Out of memory\");\n        return REDIS_ERR;\n    }\n\n    rssl->ssl = ssl;\n\n    SSL_set_mode(rssl->ssl, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);\n    SSL_set_fd(rssl->ssl, c->fd);\n    SSL_set_connect_state(rssl->ssl);\n\n    ERR_clear_error();\n\n    int rv = SSL_connect(rssl->ssl);\n    if (rv == 1) {\n        c->funcs = &redisContextSSLFuncs;\n        c->privctx = rssl;\n        return REDIS_OK;\n    }\n\n    rv = SSL_get_error(rssl->ssl, rv);\n    if (((c->flags & REDIS_BLOCK) == 0) &&\n        (rv == SSL_ERROR_WANT_READ || rv == SSL_ERROR_WANT_WRITE))\n    {\n        c->funcs = &redisContextSSLFuncs;\n        c->privctx = rssl;\n        return REDIS_OK;\n    }\n\n    if (c->err == 0) {\n        char err[512];\n        if (rv == SSL_ERROR_SYSCALL)\n            snprintf(err,sizeof(err)-1,\"SSL_connect failed: %s\",strerror(errno));\n        else {\n            unsigned long e = ERR_peek_last_error();\n            snprintf(err,sizeof(err)-1,\"SSL_connect failed: %s\",\n                    ERR_reason_error_string(e));\n        }\n        __redisSetError(c, REDIS_ERR_IO, err);\n    }\n\n    hi_free(rssl);\n    return REDIS_ERR;\n}\n\n/**\n * A wrapper around redisSSLConnect() for users who manage their own context and\n * create their own SSL object.\n */\n\nint redisInitiateSSL(redisContext *c, SSL *ssl) {\n    return redisSSLConnect(c, ssl);\n}\n\n/**\n * A wrapper around redisSSLConnect() for users who use redisSSLContext and don't\n * manage their own SSL objects.\n */\n\nint redisInitiateSSLWithContext(redisContext *c, redisSSLContext *redis_ssl_ctx)\n{\n    if (!c || !redis_ssl_ctx)\n        return REDIS_ERR;\n\n    /* We want to verify that redisSSLConnect() won't fail on this, as it will\n     * not own the SSL object in that case and we'll end up leaking.\n     */\n    if (c->privctx)\n        return REDIS_ERR;\n\n    SSL *ssl = SSL_new(redis_ssl_ctx->ssl_ctx);\n    if (!ssl) {\n        __redisSetError(c, REDIS_ERR_OTHER, \"Couldn't create new SSL instance\");\n        goto error;\n    }\n\n    if (redis_ssl_ctx->server_name) {\n        if (!SSL_set_tlsext_host_name(ssl, redis_ssl_ctx->server_name)) {\n            __redisSetError(c, REDIS_ERR_OTHER, \"Failed to set server_name/SNI\");\n            goto error;\n        }\n    }\n\n    if (redisSSLConnect(c, ssl) != REDIS_OK) {\n        goto error;\n    }\n\n    return REDIS_OK;\n\nerror:\n    if (ssl)\n        SSL_free(ssl);\n    return REDIS_ERR;\n}\n\nstatic int maybeCheckWant(redisSSL *rssl, int rv) {\n    /**\n     * If the error is WANT_READ or WANT_WRITE, the appropriate flags are set\n     * and true is returned. False is returned otherwise\n     */\n    if (rv == SSL_ERROR_WANT_READ) {\n        rssl->wantRead = 1;\n        return 1;\n    } else if (rv == SSL_ERROR_WANT_WRITE) {\n        rssl->pendingWrite = 1;\n        return 1;\n    } else {\n        return 0;\n    }\n}\n\n/**\n * Implementation of redisContextFuncs for SSL connections.\n */\n\nstatic void redisSSLFree(void *privctx){\n    redisSSL *rsc = privctx;\n\n    if (!rsc) return;\n    if (rsc->ssl) {\n        SSL_free(rsc->ssl);\n        rsc->ssl = NULL;\n    }\n    hi_free(rsc);\n}\n\nstatic ssize_t redisSSLRead(redisContext *c, char *buf, size_t bufcap) {\n    redisSSL *rssl = c->privctx;\n\n    int nread = SSL_read(rssl->ssl, buf, bufcap);\n    if (nread > 0) {\n        return nread;\n    } else if (nread == 0) {\n        __redisSetError(c, REDIS_ERR_EOF, \"Server closed the connection\");\n        return -1;\n    } else {\n        int err = SSL_get_error(rssl->ssl, nread);\n        if (c->flags & REDIS_BLOCK) {\n            /**\n             * In blocking mode, we should never end up in a situation where\n             * we get an error without it being an actual error, except\n             * in the case of EINTR, which can be spuriously received from\n             * debuggers or whatever.\n             */\n            if (errno == EINTR) {\n                return 0;\n            } else {\n                const char *msg = NULL;\n                if (errno == EAGAIN) {\n                    msg = \"Resource temporarily unavailable\";\n                }\n                __redisSetError(c, REDIS_ERR_IO, msg);\n                return -1;\n            }\n        }\n\n        /**\n         * We can very well get an EWOULDBLOCK/EAGAIN, however\n         */\n        if (maybeCheckWant(rssl, err)) {\n            return 0;\n        } else {\n            __redisSetError(c, REDIS_ERR_IO, NULL);\n            return -1;\n        }\n    }\n}\n\nstatic ssize_t redisSSLWrite(redisContext *c) {\n    redisSSL *rssl = c->privctx;\n\n    size_t len = rssl->lastLen ? rssl->lastLen : sdslen(c->obuf);\n    int rv = SSL_write(rssl->ssl, c->obuf, len);\n\n    if (rv > 0) {\n        rssl->lastLen = 0;\n    } else if (rv < 0) {\n        rssl->lastLen = len;\n\n        int err = SSL_get_error(rssl->ssl, rv);\n        if ((c->flags & REDIS_BLOCK) == 0 && maybeCheckWant(rssl, err)) {\n            return 0;\n        } else {\n            __redisSetError(c, REDIS_ERR_IO, NULL);\n            return -1;\n        }\n    }\n    return rv;\n}\n\nstatic void redisSSLAsyncRead(redisAsyncContext *ac) {\n    int rv;\n    redisSSL *rssl = ac->c.privctx;\n    redisContext *c = &ac->c;\n\n    rssl->wantRead = 0;\n\n    if (rssl->pendingWrite) {\n        int done;\n\n        /* This is probably just a write event */\n        rssl->pendingWrite = 0;\n        rv = redisBufferWrite(c, &done);\n        if (rv == REDIS_ERR) {\n            __redisAsyncDisconnect(ac);\n            return;\n        } else if (!done) {\n            _EL_ADD_WRITE(ac);\n        }\n    }\n\n    rv = redisBufferRead(c);\n    if (rv == REDIS_ERR) {\n        __redisAsyncDisconnect(ac);\n    } else {\n        _EL_ADD_READ(ac);\n        redisProcessCallbacks(ac);\n    }\n}\n\nstatic void redisSSLAsyncWrite(redisAsyncContext *ac) {\n    int rv, done = 0;\n    redisSSL *rssl = ac->c.privctx;\n    redisContext *c = &ac->c;\n\n    rssl->pendingWrite = 0;\n    rv = redisBufferWrite(c, &done);\n    if (rv == REDIS_ERR) {\n        __redisAsyncDisconnect(ac);\n        return;\n    }\n\n    if (!done) {\n        if (rssl->wantRead) {\n            /* Need to read-before-write */\n            rssl->pendingWrite = 1;\n            _EL_DEL_WRITE(ac);\n        } else {\n            /* No extra reads needed, just need to write more */\n            _EL_ADD_WRITE(ac);\n        }\n    } else {\n        /* Already done! */\n        _EL_DEL_WRITE(ac);\n    }\n\n    /* Always reschedule a read */\n    _EL_ADD_READ(ac);\n}\n\nredisContextFuncs redisContextSSLFuncs = {\n    .close = redisNetClose,\n    .free_privctx = redisSSLFree,\n    .async_read = redisSSLAsyncRead,\n    .async_write = redisSSLAsyncWrite,\n    .read = redisSSLRead,\n    .write = redisSSLWrite\n};\n\n"
  },
  {
    "path": "test.c",
    "content": "#include \"fmacros.h\"\n#include \"sockcompat.h\"\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#ifndef _WIN32\n#include <strings.h>\n#include <sys/time.h>\n#endif\n#include <assert.h>\n#include <signal.h>\n#include <errno.h>\n#include <limits.h>\n#include <math.h>\n\n#include \"hiredis.h\"\n#include \"async.h\"\n#include \"adapters/poll.h\"\n#ifdef HIREDIS_TEST_SSL\n#include \"hiredis_ssl.h\"\n#endif\n#ifdef HIREDIS_TEST_ASYNC\n#include \"adapters/libevent.h\"\n#include <event2/event.h>\n#endif\n#include \"net.h\"\n#include \"win32.h\"\n\nenum connection_type {\n    CONN_TCP,\n    CONN_UNIX,\n    CONN_FD,\n    CONN_SSL\n};\n\nstruct config {\n    enum connection_type type;\n    struct timeval connect_timeout;\n\n    struct {\n        const char *host;\n        int port;\n    } tcp;\n\n    struct {\n        const char *path;\n    } unix_sock;\n\n    struct {\n        const char *host;\n        int port;\n        const char *ca_cert;\n        const char *cert;\n        const char *key;\n    } ssl;\n};\n\nstruct privdata {\n    int dtor_counter;\n};\n\nstruct pushCounters {\n    int nil;\n    int str;\n};\n\nstatic int insecure_calloc_calls;\n\n#ifdef HIREDIS_TEST_SSL\nredisSSLContext *_ssl_ctx = NULL;\n#endif\n\n/* The following lines make up our testing \"framework\" :) */\nstatic int tests = 0, fails = 0, skips = 0;\n#define test(_s) { printf(\"#%02d \", ++tests); printf(_s); }\n#define test_cond(_c) if(_c) printf(\"\\033[0;32mPASSED\\033[0;0m\\n\"); else {printf(\"\\033[0;31mFAILED\\033[0;0m\\n\"); fails++;}\n#define test_skipped() { printf(\"\\033[01;33mSKIPPED\\033[0;0m\\n\"); skips++; }\n\nstatic void millisleep(int ms)\n{\n#ifdef _MSC_VER\n    Sleep(ms);\n#else\n    usleep(ms*1000);\n#endif\n}\n\nstatic long long usec(void) {\n#ifndef _MSC_VER\n    struct timeval tv;\n    gettimeofday(&tv,NULL);\n    return (((long long)tv.tv_sec)*1000000)+tv.tv_usec;\n#else\n    FILETIME ft;\n    GetSystemTimeAsFileTime(&ft);\n    return (((long long)ft.dwHighDateTime << 32) | ft.dwLowDateTime) / 10;\n#endif\n}\n\n/* The assert() calls below have side effects, so we need assert()\n * even if we are compiling without asserts (-DNDEBUG). */\n#ifdef NDEBUG\n#undef assert\n#define assert(e) (void)(e)\n#endif\n\n#define redisTestPanic(msg) \\\n    do { \\\n        fprintf(stderr, \"PANIC: %s (In function \\\"%s\\\", file \\\"%s\\\", line %d)\\n\", \\\n                msg, __func__, __FILE__, __LINE__); \\\n        exit(1); \\\n    } while (1)\n\n/* Helper to extract Redis version information.  Aborts on any failure. */\n#define REDIS_VERSION_FIELD \"redis_version:\"\nvoid get_redis_version(redisContext *c, int *majorptr, int *minorptr) {\n    redisReply *reply;\n    char *eptr, *s, *e;\n    int major, minor;\n\n    reply = redisCommand(c, \"INFO\");\n    if (reply == NULL || c->err || reply->type != REDIS_REPLY_STRING)\n        goto abort;\n    if ((s = strstr(reply->str, REDIS_VERSION_FIELD)) == NULL)\n        goto abort;\n\n    s += strlen(REDIS_VERSION_FIELD);\n\n    /* We need a field terminator and at least 'x.y.z' (5) bytes of data */\n    if ((e = strstr(s, \"\\r\\n\")) == NULL || (e - s) < 5)\n        goto abort;\n\n    /* Extract version info */\n    major = strtol(s, &eptr, 10);\n    if (*eptr != '.') goto abort;\n    minor = strtol(eptr+1, NULL, 10);\n\n    /* Push info the caller wants */\n    if (majorptr) *majorptr = major;\n    if (minorptr) *minorptr = minor;\n\n    freeReplyObject(reply);\n    return;\n\nabort:\n    freeReplyObject(reply);\n    fprintf(stderr, \"Error:  Cannot determine Redis version, aborting\\n\");\n    exit(1);\n}\n\nstatic redisContext *select_database(redisContext *c) {\n    redisReply *reply;\n\n    /* Switch to DB 9 for testing, now that we know we can chat. */\n    reply = redisCommand(c,\"SELECT 9\");\n    assert(reply != NULL);\n    freeReplyObject(reply);\n\n    /* Make sure the DB is empty */\n    reply = redisCommand(c,\"DBSIZE\");\n    assert(reply != NULL);\n    if (reply->type == REDIS_REPLY_INTEGER && reply->integer == 0) {\n        /* Awesome, DB 9 is empty and we can continue. */\n        freeReplyObject(reply);\n    } else {\n        printf(\"Database #9 is not empty, test can not continue\\n\");\n        exit(1);\n    }\n\n    return c;\n}\n\n/* Switch protocol */\nstatic void send_hello(redisContext *c, int version) {\n    redisReply *reply;\n    int expected;\n\n    reply = redisCommand(c, \"HELLO %d\", version);\n    expected = version == 3 ? REDIS_REPLY_MAP : REDIS_REPLY_ARRAY;\n    assert(reply != NULL && reply->type == expected);\n    freeReplyObject(reply);\n}\n\n/* Togggle client tracking */\nstatic void send_client_tracking(redisContext *c, const char *str) {\n    redisReply *reply;\n\n    reply = redisCommand(c, \"CLIENT TRACKING %s\", str);\n    assert(reply != NULL && reply->type == REDIS_REPLY_STATUS);\n    freeReplyObject(reply);\n}\n\nstatic int disconnect(redisContext *c, int keep_fd) {\n    redisReply *reply;\n\n    /* Make sure we're on DB 9. */\n    reply = redisCommand(c,\"SELECT 9\");\n    assert(reply != NULL);\n    freeReplyObject(reply);\n    reply = redisCommand(c,\"FLUSHDB\");\n    assert(reply != NULL);\n    freeReplyObject(reply);\n\n    /* Free the context as well, but keep the fd if requested. */\n    if (keep_fd)\n        return redisFreeKeepFd(c);\n    redisFree(c);\n    return -1;\n}\n\nstatic void do_ssl_handshake(redisContext *c) {\n#ifdef HIREDIS_TEST_SSL\n    redisInitiateSSLWithContext(c, _ssl_ctx);\n    if (c->err) {\n        printf(\"SSL error: %s\\n\", c->errstr);\n        redisFree(c);\n        exit(1);\n    }\n#else\n    (void) c;\n#endif\n}\n\nstatic redisContext *do_connect(struct config config) {\n    redisContext *c = NULL;\n\n    if (config.type == CONN_TCP) {\n        c = redisConnect(config.tcp.host, config.tcp.port);\n    } else if (config.type == CONN_SSL) {\n        c = redisConnect(config.ssl.host, config.ssl.port);\n    } else if (config.type == CONN_UNIX) {\n        c = redisConnectUnix(config.unix_sock.path);\n    } else if (config.type == CONN_FD) {\n        /* Create a dummy connection just to get an fd to inherit */\n        redisContext *dummy_ctx = redisConnectUnix(config.unix_sock.path);\n        if (dummy_ctx) {\n            int fd = disconnect(dummy_ctx, 1);\n            printf(\"Connecting to inherited fd %d\\n\", fd);\n            c = redisConnectFd(fd);\n        }\n    } else {\n        redisTestPanic(\"Unknown connection type!\");\n    }\n\n    if (c == NULL) {\n        printf(\"Connection error: can't allocate redis context\\n\");\n        exit(1);\n    } else if (c->err) {\n        printf(\"Connection error: %s\\n\", c->errstr);\n        redisFree(c);\n        exit(1);\n    }\n\n    if (config.type == CONN_SSL) {\n        do_ssl_handshake(c);\n    }\n\n    return select_database(c);\n}\n\nstatic void do_reconnect(redisContext *c, struct config config) {\n    redisReconnect(c);\n\n    if (config.type == CONN_SSL) {\n        do_ssl_handshake(c);\n    }\n}\n\nstatic void test_format_commands(void) {\n    char *cmd;\n    int len;\n\n    test(\"Format command without interpolation: \");\n    len = redisFormatCommand(&cmd,\"SET foo bar\");\n    test_cond(strncmp(cmd,\"*3\\r\\n$3\\r\\nSET\\r\\n$3\\r\\nfoo\\r\\n$3\\r\\nbar\\r\\n\",len) == 0 &&\n        len == 4+4+(3+2)+4+(3+2)+4+(3+2));\n    hi_free(cmd);\n\n    test(\"Format command with %%s string interpolation: \");\n    len = redisFormatCommand(&cmd,\"SET %s %s\",\"foo\",\"bar\");\n    test_cond(strncmp(cmd,\"*3\\r\\n$3\\r\\nSET\\r\\n$3\\r\\nfoo\\r\\n$3\\r\\nbar\\r\\n\",len) == 0 &&\n        len == 4+4+(3+2)+4+(3+2)+4+(3+2));\n    hi_free(cmd);\n\n    test(\"Format command with %%s and an empty string: \");\n    len = redisFormatCommand(&cmd,\"SET %s %s\",\"foo\",\"\");\n    test_cond(strncmp(cmd,\"*3\\r\\n$3\\r\\nSET\\r\\n$3\\r\\nfoo\\r\\n$0\\r\\n\\r\\n\",len) == 0 &&\n        len == 4+4+(3+2)+4+(3+2)+4+(0+2));\n    hi_free(cmd);\n\n    test(\"Format command with an empty string in between proper interpolations: \");\n    len = redisFormatCommand(&cmd,\"SET %s %s\",\"\",\"foo\");\n    test_cond(strncmp(cmd,\"*3\\r\\n$3\\r\\nSET\\r\\n$0\\r\\n\\r\\n$3\\r\\nfoo\\r\\n\",len) == 0 &&\n        len == 4+4+(3+2)+4+(0+2)+4+(3+2));\n    hi_free(cmd);\n\n    test(\"Format command with %%b string interpolation: \");\n    len = redisFormatCommand(&cmd,\"SET %b %b\",\"foo\",(size_t)3,\"b\\0r\",(size_t)3);\n    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 &&\n        len == 4+4+(3+2)+4+(3+2)+4+(3+2));\n    hi_free(cmd);\n\n    test(\"Format command with %%b and an empty string: \");\n    len = redisFormatCommand(&cmd,\"SET %b %b\",\"foo\",(size_t)3,\"\",(size_t)0);\n    test_cond(strncmp(cmd,\"*3\\r\\n$3\\r\\nSET\\r\\n$3\\r\\nfoo\\r\\n$0\\r\\n\\r\\n\",len) == 0 &&\n        len == 4+4+(3+2)+4+(3+2)+4+(0+2));\n    hi_free(cmd);\n\n    test(\"Format command with literal %%: \");\n    len = redisFormatCommand(&cmd,\"SET %% %%\");\n    test_cond(strncmp(cmd,\"*3\\r\\n$3\\r\\nSET\\r\\n$1\\r\\n%\\r\\n$1\\r\\n%\\r\\n\",len) == 0 &&\n        len == 4+4+(3+2)+4+(1+2)+4+(1+2));\n    hi_free(cmd);\n\n    /* Vararg width depends on the type. These tests make sure that the\n     * width is correctly determined using the format and subsequent varargs\n     * can correctly be interpolated. */\n#define INTEGER_WIDTH_TEST(fmt, type) do {                                                \\\n    type value = 123;                                                                     \\\n    test(\"Format command with printf-delegation (\" #type \"): \");                          \\\n    len = redisFormatCommand(&cmd,\"key:%08\" fmt \" str:%s\", value, \"hello\");               \\\n    test_cond(strncmp(cmd,\"*2\\r\\n$12\\r\\nkey:00000123\\r\\n$9\\r\\nstr:hello\\r\\n\",len) == 0 && \\\n        len == 4+5+(12+2)+4+(9+2));                                                       \\\n    hi_free(cmd);                                                                         \\\n} while(0)\n\n#define FLOAT_WIDTH_TEST(type) do {                                                       \\\n    type value = 123.0;                                                                   \\\n    test(\"Format command with printf-delegation (\" #type \"): \");                          \\\n    len = redisFormatCommand(&cmd,\"key:%08.3f str:%s\", value, \"hello\");                   \\\n    test_cond(strncmp(cmd,\"*2\\r\\n$12\\r\\nkey:0123.000\\r\\n$9\\r\\nstr:hello\\r\\n\",len) == 0 && \\\n        len == 4+5+(12+2)+4+(9+2));                                                       \\\n    hi_free(cmd);                                                                         \\\n} while(0)\n\n    INTEGER_WIDTH_TEST(\"d\", int);\n    INTEGER_WIDTH_TEST(\"hhd\", char);\n    INTEGER_WIDTH_TEST(\"hd\", short);\n    INTEGER_WIDTH_TEST(\"ld\", long);\n    INTEGER_WIDTH_TEST(\"lld\", long long);\n    INTEGER_WIDTH_TEST(\"u\", unsigned int);\n    INTEGER_WIDTH_TEST(\"hhu\", unsigned char);\n    INTEGER_WIDTH_TEST(\"hu\", unsigned short);\n    INTEGER_WIDTH_TEST(\"lu\", unsigned long);\n    INTEGER_WIDTH_TEST(\"llu\", unsigned long long);\n    FLOAT_WIDTH_TEST(float);\n    FLOAT_WIDTH_TEST(double);\n\n    test(\"Format command with unhandled printf format (specifier 'p' not supported): \");\n    len = redisFormatCommand(&cmd,\"key:%08p %b\",(void*)1234,\"foo\",(size_t)3);\n    test_cond(len == -1);\n\n    test(\"Format command with invalid printf format (specifier missing): \");\n    len = redisFormatCommand(&cmd,\"%-\");\n    test_cond(len == -1);\n\n    const char *argv[3];\n    argv[0] = \"SET\";\n    argv[1] = \"foo\\0xxx\";\n    argv[2] = \"bar\";\n    size_t lens[3] = { 3, 7, 3 };\n    int argc = 3;\n\n    test(\"Format command by passing argc/argv without lengths: \");\n    len = redisFormatCommandArgv(&cmd,argc,argv,NULL);\n    test_cond(strncmp(cmd,\"*3\\r\\n$3\\r\\nSET\\r\\n$3\\r\\nfoo\\r\\n$3\\r\\nbar\\r\\n\",len) == 0 &&\n        len == 4+4+(3+2)+4+(3+2)+4+(3+2));\n    hi_free(cmd);\n\n    test(\"Format command by passing argc/argv with lengths: \");\n    len = redisFormatCommandArgv(&cmd,argc,argv,lens);\n    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 &&\n        len == 4+4+(3+2)+4+(7+2)+4+(3+2));\n    hi_free(cmd);\n\n    sds sds_cmd;\n\n    sds_cmd = NULL;\n    test(\"Format command into sds by passing argc/argv without lengths: \");\n    len = redisFormatSdsCommandArgv(&sds_cmd,argc,argv,NULL);\n    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 &&\n        len == 4+4+(3+2)+4+(3+2)+4+(3+2));\n    sdsfree(sds_cmd);\n\n    sds_cmd = NULL;\n    test(\"Format command into sds by passing argc/argv with lengths: \");\n    len = redisFormatSdsCommandArgv(&sds_cmd,argc,argv,lens);\n    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 &&\n        len == 4+4+(3+2)+4+(7+2)+4+(3+2));\n    sdsfree(sds_cmd);\n}\n\nstatic void test_append_formatted_commands(struct config config) {\n    redisContext *c;\n    redisReply *reply;\n    char *cmd;\n    int len;\n\n    c = do_connect(config);\n\n    test(\"Append format command: \");\n\n    len = redisFormatCommand(&cmd, \"SET foo bar\");\n\n    test_cond(redisAppendFormattedCommand(c, cmd, len) == REDIS_OK);\n\n    assert(redisGetReply(c, (void*)&reply) == REDIS_OK);\n\n    hi_free(cmd);\n    freeReplyObject(reply);\n\n    disconnect(c, 0);\n}\n\nstatic void test_tcp_options(struct config cfg) {\n    redisContext *c;\n\n    c = do_connect(cfg);\n\n    test(\"We can enable TCP_KEEPALIVE: \");\n    test_cond(redisEnableKeepAlive(c) == REDIS_OK);\n\n#ifdef TCP_USER_TIMEOUT\n    test(\"We can set TCP_USER_TIMEOUT: \");\n    test_cond(redisSetTcpUserTimeout(c, 100) == REDIS_OK);\n#else\n    test(\"Setting TCP_USER_TIMEOUT errors when unsupported: \");\n    test_cond(redisSetTcpUserTimeout(c, 100) == REDIS_ERR && c->err == REDIS_ERR_IO);\n#endif\n\n    redisFree(c);\n}\n\nstatic void test_unix_keepalive(struct config cfg) {\n    redisContext *c;\n    redisReply *r;\n\n    c = do_connect(cfg);\n\n    test(\"Setting TCP_KEEPALIVE on a unix socket returns an error: \");\n    test_cond(redisEnableKeepAlive(c) == REDIS_ERR && c->err == 0);\n\n    test(\"Setting TCP_KEEPALIVE on a unix socket doesn't break the connection: \");\n    r = redisCommand(c, \"PING\");\n    test_cond(r != NULL && r->type == REDIS_REPLY_STATUS && r->len == 4 &&\n              !memcmp(r->str, \"PONG\", 4));\n    freeReplyObject(r);\n\n    redisFree(c);\n}\n\nstatic void test_reply_reader(void) {\n    redisReader *reader;\n    void *reply, *root;\n    int ret;\n    int i;\n\n    test(\"Error handling in reply parser: \");\n    reader = redisReaderCreate();\n    redisReaderFeed(reader,(char*)\"@foo\\r\\n\",6);\n    ret = redisReaderGetReply(reader,NULL);\n    test_cond(ret == REDIS_ERR &&\n              strcasecmp(reader->errstr,\"Protocol error, got \\\"@\\\" as reply type byte\") == 0);\n    redisReaderFree(reader);\n\n    /* when the reply already contains multiple items, they must be free'd\n     * on an error. valgrind will bark when this doesn't happen. */\n    test(\"Memory cleanup in reply parser: \");\n    reader = redisReaderCreate();\n    redisReaderFeed(reader,(char*)\"*2\\r\\n\",4);\n    redisReaderFeed(reader,(char*)\"$5\\r\\nhello\\r\\n\",11);\n    redisReaderFeed(reader,(char*)\"@foo\\r\\n\",6);\n    ret = redisReaderGetReply(reader,NULL);\n    test_cond(ret == REDIS_ERR &&\n              strcasecmp(reader->errstr,\"Protocol error, got \\\"@\\\" as reply type byte\") == 0);\n    redisReaderFree(reader);\n\n    reader = redisReaderCreate();\n    test(\"Can handle arbitrarily nested multi-bulks: \");\n    for (i = 0; i < 128; i++) {\n        redisReaderFeed(reader,(char*)\"*1\\r\\n\", 4);\n    }\n    redisReaderFeed(reader,(char*)\"$6\\r\\nLOLWUT\\r\\n\",12);\n    ret = redisReaderGetReply(reader,&reply);\n    root = reply; /* Keep track of the root reply */\n    test_cond(ret == REDIS_OK &&\n        ((redisReply*)reply)->type == REDIS_REPLY_ARRAY &&\n        ((redisReply*)reply)->elements == 1);\n\n    test(\"Can parse arbitrarily nested multi-bulks correctly: \");\n    while(i--) {\n        assert(reply != NULL && ((redisReply*)reply)->type == REDIS_REPLY_ARRAY);\n        reply = ((redisReply*)reply)->element[0];\n    }\n    test_cond(((redisReply*)reply)->type == REDIS_REPLY_STRING &&\n        !memcmp(((redisReply*)reply)->str, \"LOLWUT\", 6));\n    freeReplyObject(root);\n    redisReaderFree(reader);\n\n    test(\"Correctly parses LLONG_MAX: \");\n    reader = redisReaderCreate();\n    redisReaderFeed(reader, \":9223372036854775807\\r\\n\",22);\n    ret = redisReaderGetReply(reader,&reply);\n    test_cond(ret == REDIS_OK &&\n            ((redisReply*)reply)->type == REDIS_REPLY_INTEGER &&\n            ((redisReply*)reply)->integer == LLONG_MAX);\n    freeReplyObject(reply);\n    redisReaderFree(reader);\n\n    test(\"Set error when > LLONG_MAX: \");\n    reader = redisReaderCreate();\n    redisReaderFeed(reader, \":9223372036854775808\\r\\n\",22);\n    ret = redisReaderGetReply(reader,&reply);\n    test_cond(ret == REDIS_ERR &&\n              strcasecmp(reader->errstr,\"Bad integer value\") == 0);\n    freeReplyObject(reply);\n    redisReaderFree(reader);\n\n    test(\"Correctly parses LLONG_MIN: \");\n    reader = redisReaderCreate();\n    redisReaderFeed(reader, \":-9223372036854775808\\r\\n\",23);\n    ret = redisReaderGetReply(reader,&reply);\n    test_cond(ret == REDIS_OK &&\n            ((redisReply*)reply)->type == REDIS_REPLY_INTEGER &&\n            ((redisReply*)reply)->integer == LLONG_MIN);\n    freeReplyObject(reply);\n    redisReaderFree(reader);\n\n    test(\"Set error when < LLONG_MIN: \");\n    reader = redisReaderCreate();\n    redisReaderFeed(reader, \":-9223372036854775809\\r\\n\",23);\n    ret = redisReaderGetReply(reader,&reply);\n    test_cond(ret == REDIS_ERR &&\n              strcasecmp(reader->errstr,\"Bad integer value\") == 0);\n    freeReplyObject(reply);\n    redisReaderFree(reader);\n\n    test(\"Set error when array < -1: \");\n    reader = redisReaderCreate();\n    redisReaderFeed(reader, \"*-2\\r\\n+asdf\\r\\n\",12);\n    ret = redisReaderGetReply(reader,&reply);\n    test_cond(ret == REDIS_ERR &&\n              strcasecmp(reader->errstr,\"Multi-bulk length out of range\") == 0);\n    freeReplyObject(reply);\n    redisReaderFree(reader);\n\n    test(\"Set error when bulk < -1: \");\n    reader = redisReaderCreate();\n    redisReaderFeed(reader, \"$-2\\r\\nasdf\\r\\n\",11);\n    ret = redisReaderGetReply(reader,&reply);\n    test_cond(ret == REDIS_ERR &&\n              strcasecmp(reader->errstr,\"Bulk string length out of range\") == 0);\n    freeReplyObject(reply);\n    redisReaderFree(reader);\n\n    test(\"Can configure maximum multi-bulk elements: \");\n    reader = redisReaderCreate();\n    reader->maxelements = 1024;\n    redisReaderFeed(reader, \"*1025\\r\\n\", 7);\n    ret = redisReaderGetReply(reader,&reply);\n    test_cond(ret == REDIS_ERR &&\n              strcasecmp(reader->errstr, \"Multi-bulk length out of range\") == 0);\n    freeReplyObject(reply);\n    redisReaderFree(reader);\n\n    test(\"Multi-bulk never overflows regardless of maxelements: \");\n    size_t bad_mbulk_len = (SIZE_MAX / sizeof(void *)) + 3;\n    char bad_mbulk_reply[100];\n    snprintf(bad_mbulk_reply, sizeof(bad_mbulk_reply), \"*%llu\\r\\n+asdf\\r\\n\",\n        (unsigned long long) bad_mbulk_len);\n\n    reader = redisReaderCreate();\n    reader->maxelements = 0;    /* Don't rely on default limit */\n    redisReaderFeed(reader, bad_mbulk_reply, strlen(bad_mbulk_reply));\n    ret = redisReaderGetReply(reader,&reply);\n    test_cond(ret == REDIS_ERR && strcasecmp(reader->errstr, \"Out of memory\") == 0);\n    freeReplyObject(reply);\n    redisReaderFree(reader);\n\n#if LLONG_MAX > SIZE_MAX\n    test(\"Set error when array > SIZE_MAX: \");\n    reader = redisReaderCreate();\n    redisReaderFeed(reader, \"*9223372036854775807\\r\\n+asdf\\r\\n\",29);\n    ret = redisReaderGetReply(reader,&reply);\n    test_cond(ret == REDIS_ERR &&\n            strcasecmp(reader->errstr,\"Multi-bulk length out of range\") == 0);\n    freeReplyObject(reply);\n    redisReaderFree(reader);\n\n    test(\"Set error when bulk > SIZE_MAX: \");\n    reader = redisReaderCreate();\n    redisReaderFeed(reader, \"$9223372036854775807\\r\\nasdf\\r\\n\",28);\n    ret = redisReaderGetReply(reader,&reply);\n    test_cond(ret == REDIS_ERR &&\n            strcasecmp(reader->errstr,\"Bulk string length out of range\") == 0);\n    freeReplyObject(reply);\n    redisReaderFree(reader);\n#endif\n\n    test(\"Works with NULL functions for reply: \");\n    reader = redisReaderCreate();\n    reader->fn = NULL;\n    redisReaderFeed(reader,(char*)\"+OK\\r\\n\",5);\n    ret = redisReaderGetReply(reader,&reply);\n    test_cond(ret == REDIS_OK && reply == (void*)REDIS_REPLY_STATUS);\n    redisReaderFree(reader);\n\n    test(\"Works when a single newline (\\\\r\\\\n) covers two calls to feed: \");\n    reader = redisReaderCreate();\n    reader->fn = NULL;\n    redisReaderFeed(reader,(char*)\"+OK\\r\",4);\n    ret = redisReaderGetReply(reader,&reply);\n    assert(ret == REDIS_OK && reply == NULL);\n    redisReaderFeed(reader,(char*)\"\\n\",1);\n    ret = redisReaderGetReply(reader,&reply);\n    test_cond(ret == REDIS_OK && reply == (void*)REDIS_REPLY_STATUS);\n    redisReaderFree(reader);\n\n    test(\"Don't reset state after protocol error: \");\n    reader = redisReaderCreate();\n    reader->fn = NULL;\n    redisReaderFeed(reader,(char*)\"x\",1);\n    ret = redisReaderGetReply(reader,&reply);\n    assert(ret == REDIS_ERR);\n    ret = redisReaderGetReply(reader,&reply);\n    test_cond(ret == REDIS_ERR && reply == NULL);\n    redisReaderFree(reader);\n\n    test(\"Don't reset state after protocol error(not segfault): \");\n    reader = redisReaderCreate();\n    redisReaderFeed(reader,(char*)\"*3\\r\\n$3\\r\\nSET\\r\\n$5\\r\\nhello\\r\\n$\", 25);\n    ret = redisReaderGetReply(reader,&reply);\n    assert(ret == REDIS_OK);\n    redisReaderFeed(reader,(char*)\"3\\r\\nval\\r\\n\", 8);\n    ret = redisReaderGetReply(reader,&reply);\n    test_cond(ret == REDIS_OK &&\n        ((redisReply*)reply)->type == REDIS_REPLY_ARRAY &&\n        ((redisReply*)reply)->elements == 3);\n    freeReplyObject(reply);\n    redisReaderFree(reader);\n\n    /* Regression test for issue #45 on GitHub. */\n    test(\"Don't do empty allocation for empty multi bulk: \");\n    reader = redisReaderCreate();\n    redisReaderFeed(reader,(char*)\"*0\\r\\n\",4);\n    ret = redisReaderGetReply(reader,&reply);\n    test_cond(ret == REDIS_OK &&\n        ((redisReply*)reply)->type == REDIS_REPLY_ARRAY &&\n        ((redisReply*)reply)->elements == 0);\n    freeReplyObject(reply);\n    redisReaderFree(reader);\n\n    /* RESP3 verbatim strings (GitHub issue #802) */\n    test(\"Can parse RESP3 verbatim strings: \");\n    reader = redisReaderCreate();\n    redisReaderFeed(reader,(char*)\"=10\\r\\ntxt:LOLWUT\\r\\n\",17);\n    ret = redisReaderGetReply(reader,&reply);\n    test_cond(ret == REDIS_OK &&\n        ((redisReply*)reply)->type == REDIS_REPLY_VERB &&\n         !memcmp(((redisReply*)reply)->str,\"LOLWUT\", 6));\n    freeReplyObject(reply);\n    redisReaderFree(reader);\n\n    /* RESP3 push messages (Github issue #815) */\n    test(\"Can parse RESP3 push messages: \");\n    reader = redisReaderCreate();\n    redisReaderFeed(reader,(char*)\">2\\r\\n$6\\r\\nLOLWUT\\r\\n:42\\r\\n\",21);\n    ret = redisReaderGetReply(reader,&reply);\n    test_cond(ret == REDIS_OK &&\n        ((redisReply*)reply)->type == REDIS_REPLY_PUSH &&\n        ((redisReply*)reply)->elements == 2 &&\n        ((redisReply*)reply)->element[0]->type == REDIS_REPLY_STRING &&\n        !memcmp(((redisReply*)reply)->element[0]->str,\"LOLWUT\",6) &&\n        ((redisReply*)reply)->element[1]->type == REDIS_REPLY_INTEGER &&\n        ((redisReply*)reply)->element[1]->integer == 42);\n    freeReplyObject(reply);\n    redisReaderFree(reader);\n\n    test(\"Can parse RESP3 doubles: \");\n    reader = redisReaderCreate();\n    redisReaderFeed(reader, \",3.14159265358979323846\\r\\n\",25);\n    ret = redisReaderGetReply(reader,&reply);\n    test_cond(ret == REDIS_OK &&\n              ((redisReply*)reply)->type == REDIS_REPLY_DOUBLE &&\n              fabs(((redisReply*)reply)->dval - 3.14159265358979323846) < 0.00000001 &&\n              ((redisReply*)reply)->len == 22 &&\n              strcmp(((redisReply*)reply)->str, \"3.14159265358979323846\") == 0);\n    freeReplyObject(reply);\n    redisReaderFree(reader);\n\n    test(\"Set error on invalid RESP3 double: \");\n    reader = redisReaderCreate();\n    redisReaderFeed(reader, \",3.14159\\000265358979323846\\r\\n\",26);\n    ret = redisReaderGetReply(reader,&reply);\n    test_cond(ret == REDIS_ERR &&\n              strcasecmp(reader->errstr,\"Bad double value\") == 0);\n    freeReplyObject(reply);\n    redisReaderFree(reader);\n\n    test(\"Correctly parses RESP3 double INFINITY: \");\n    reader = redisReaderCreate();\n    redisReaderFeed(reader, \",inf\\r\\n\",6);\n    ret = redisReaderGetReply(reader,&reply);\n    test_cond(ret == REDIS_OK &&\n              ((redisReply*)reply)->type == REDIS_REPLY_DOUBLE &&\n              isinf(((redisReply*)reply)->dval) &&\n              ((redisReply*)reply)->dval > 0);\n    freeReplyObject(reply);\n    redisReaderFree(reader);\n\n    test(\"Correctly parses RESP3 double NaN: \");\n    reader = redisReaderCreate();\n    redisReaderFeed(reader, \",nan\\r\\n\",6);\n    ret = redisReaderGetReply(reader,&reply);\n    test_cond(ret == REDIS_OK &&\n              ((redisReply*)reply)->type == REDIS_REPLY_DOUBLE &&\n              isnan(((redisReply*)reply)->dval));\n    freeReplyObject(reply);\n    redisReaderFree(reader);\n\n    test(\"Correctly parses RESP3 double -Nan: \");\n    reader = redisReaderCreate();\n    redisReaderFeed(reader, \",-nan\\r\\n\", 7);\n    ret = redisReaderGetReply(reader, &reply);\n    test_cond(ret == REDIS_OK &&\n              ((redisReply*)reply)->type == REDIS_REPLY_DOUBLE &&\n              isnan(((redisReply*)reply)->dval));\n    freeReplyObject(reply);\n    redisReaderFree(reader);\n\n    test(\"Can parse RESP3 nil: \");\n    reader = redisReaderCreate();\n    redisReaderFeed(reader, \"_\\r\\n\",3);\n    ret = redisReaderGetReply(reader,&reply);\n    test_cond(ret == REDIS_OK &&\n              ((redisReply*)reply)->type == REDIS_REPLY_NIL);\n    freeReplyObject(reply);\n    redisReaderFree(reader);\n\n    test(\"Set error on invalid RESP3 nil: \");\n    reader = redisReaderCreate();\n    redisReaderFeed(reader, \"_nil\\r\\n\",6);\n    ret = redisReaderGetReply(reader,&reply);\n    test_cond(ret == REDIS_ERR &&\n              strcasecmp(reader->errstr,\"Bad nil value\") == 0);\n    freeReplyObject(reply);\n    redisReaderFree(reader);\n\n    test(\"Can parse RESP3 bool (true): \");\n    reader = redisReaderCreate();\n    redisReaderFeed(reader, \"#t\\r\\n\",4);\n    ret = redisReaderGetReply(reader,&reply);\n    test_cond(ret == REDIS_OK &&\n              ((redisReply*)reply)->type == REDIS_REPLY_BOOL &&\n              ((redisReply*)reply)->integer);\n    freeReplyObject(reply);\n    redisReaderFree(reader);\n\n    test(\"Can parse RESP3 bool (false): \");\n    reader = redisReaderCreate();\n    redisReaderFeed(reader, \"#f\\r\\n\",4);\n    ret = redisReaderGetReply(reader,&reply);\n    test_cond(ret == REDIS_OK &&\n              ((redisReply*)reply)->type == REDIS_REPLY_BOOL &&\n              !((redisReply*)reply)->integer);\n    freeReplyObject(reply);\n    redisReaderFree(reader);\n\n    test(\"Set error on invalid RESP3 bool: \");\n    reader = redisReaderCreate();\n    redisReaderFeed(reader, \"#foobar\\r\\n\",9);\n    ret = redisReaderGetReply(reader,&reply);\n    test_cond(ret == REDIS_ERR &&\n              strcasecmp(reader->errstr,\"Bad bool value\") == 0);\n    freeReplyObject(reply);\n    redisReaderFree(reader);\n\n    test(\"Can parse RESP3 map: \");\n    reader = redisReaderCreate();\n    redisReaderFeed(reader, \"%2\\r\\n+first\\r\\n:123\\r\\n$6\\r\\nsecond\\r\\n#t\\r\\n\",34);\n    ret = redisReaderGetReply(reader,&reply);\n    test_cond(ret == REDIS_OK &&\n        ((redisReply*)reply)->type == REDIS_REPLY_MAP &&\n        ((redisReply*)reply)->elements == 4 &&\n        ((redisReply*)reply)->element[0]->type == REDIS_REPLY_STATUS &&\n        ((redisReply*)reply)->element[0]->len == 5 &&\n        !strcmp(((redisReply*)reply)->element[0]->str,\"first\") &&\n        ((redisReply*)reply)->element[1]->type == REDIS_REPLY_INTEGER &&\n        ((redisReply*)reply)->element[1]->integer == 123 &&\n        ((redisReply*)reply)->element[2]->type == REDIS_REPLY_STRING &&\n        ((redisReply*)reply)->element[2]->len == 6 &&\n        !strcmp(((redisReply*)reply)->element[2]->str,\"second\") &&\n        ((redisReply*)reply)->element[3]->type == REDIS_REPLY_BOOL &&\n        ((redisReply*)reply)->element[3]->integer);\n    freeReplyObject(reply);\n    redisReaderFree(reader);\n\n    test(\"Can parse RESP3 attribute: \");\n    reader = redisReaderCreate();\n    redisReaderFeed(reader, \"|2\\r\\n+foo\\r\\n:123\\r\\n+bar\\r\\n#t\\r\\n\",26);\n    ret = redisReaderGetReply(reader,&reply);\n    test_cond(ret == REDIS_OK &&\n        ((redisReply*)reply)->type == REDIS_REPLY_ATTR &&\n        ((redisReply*)reply)->elements == 4 &&\n        ((redisReply*)reply)->element[0]->type == REDIS_REPLY_STATUS &&\n        ((redisReply*)reply)->element[0]->len == 3 &&\n        !strcmp(((redisReply*)reply)->element[0]->str,\"foo\") &&\n        ((redisReply*)reply)->element[1]->type == REDIS_REPLY_INTEGER &&\n        ((redisReply*)reply)->element[1]->integer == 123 &&\n        ((redisReply*)reply)->element[2]->type == REDIS_REPLY_STATUS &&\n        ((redisReply*)reply)->element[2]->len == 3 &&\n        !strcmp(((redisReply*)reply)->element[2]->str,\"bar\") &&\n        ((redisReply*)reply)->element[3]->type == REDIS_REPLY_BOOL &&\n        ((redisReply*)reply)->element[3]->integer);\n    freeReplyObject(reply);\n    redisReaderFree(reader);\n\n    test(\"Can parse RESP3 set: \");\n    reader = redisReaderCreate();\n    redisReaderFeed(reader, \"~5\\r\\n+orange\\r\\n$5\\r\\napple\\r\\n#f\\r\\n:100\\r\\n:999\\r\\n\",40);\n    ret = redisReaderGetReply(reader,&reply);\n    test_cond(ret == REDIS_OK &&\n        ((redisReply*)reply)->type == REDIS_REPLY_SET &&\n        ((redisReply*)reply)->elements == 5 &&\n        ((redisReply*)reply)->element[0]->type == REDIS_REPLY_STATUS &&\n        ((redisReply*)reply)->element[0]->len == 6 &&\n        !strcmp(((redisReply*)reply)->element[0]->str,\"orange\") &&\n        ((redisReply*)reply)->element[1]->type == REDIS_REPLY_STRING &&\n        ((redisReply*)reply)->element[1]->len == 5 &&\n        !strcmp(((redisReply*)reply)->element[1]->str,\"apple\") &&\n        ((redisReply*)reply)->element[2]->type == REDIS_REPLY_BOOL &&\n        !((redisReply*)reply)->element[2]->integer &&\n        ((redisReply*)reply)->element[3]->type == REDIS_REPLY_INTEGER &&\n        ((redisReply*)reply)->element[3]->integer == 100 &&\n        ((redisReply*)reply)->element[4]->type == REDIS_REPLY_INTEGER &&\n        ((redisReply*)reply)->element[4]->integer == 999);\n    freeReplyObject(reply);\n    redisReaderFree(reader);\n\n    test(\"Can parse RESP3 bignum: \");\n    reader = redisReaderCreate();\n    redisReaderFeed(reader,\"(3492890328409238509324850943850943825024385\\r\\n\",46);\n    ret = redisReaderGetReply(reader,&reply);\n    test_cond(ret == REDIS_OK &&\n        ((redisReply*)reply)->type == REDIS_REPLY_BIGNUM &&\n        ((redisReply*)reply)->len == 43 &&\n        !strcmp(((redisReply*)reply)->str,\"3492890328409238509324850943850943825024385\"));\n    freeReplyObject(reply);\n    redisReaderFree(reader);\n\n    test(\"Can parse RESP3 doubles in an array: \");\n    reader = redisReaderCreate();\n    redisReaderFeed(reader, \"*1\\r\\n,3.14159265358979323846\\r\\n\",29);\n    ret = redisReaderGetReply(reader,&reply);\n    test_cond(ret == REDIS_OK &&\n        ((redisReply*)reply)->type == REDIS_REPLY_ARRAY &&\n        ((redisReply*)reply)->elements == 1 &&\n        ((redisReply*)reply)->element[0]->type == REDIS_REPLY_DOUBLE &&\n        fabs(((redisReply*)reply)->element[0]->dval - 3.14159265358979323846) < 0.00000001 &&\n        ((redisReply*)reply)->element[0]->len == 22 &&\n        strcmp(((redisReply*)reply)->element[0]->str, \"3.14159265358979323846\") == 0);\n    freeReplyObject(reply);\n    redisReaderFree(reader);\n}\n\nstatic void test_free_null(void) {\n    void *redisCtx = NULL;\n    void *reply = NULL;\n\n    test(\"Don't fail when redisFree is passed a NULL value: \");\n    redisFree(redisCtx);\n    test_cond(redisCtx == NULL);\n\n    test(\"Don't fail when freeReplyObject is passed a NULL value: \");\n    freeReplyObject(reply);\n    test_cond(reply == NULL);\n}\n\nstatic void *hi_malloc_fail(size_t size) {\n    (void)size;\n    return NULL;\n}\n\nstatic void *hi_calloc_fail(size_t nmemb, size_t size) {\n    (void)nmemb;\n    (void)size;\n    return NULL;\n}\n\nstatic void *hi_calloc_insecure(size_t nmemb, size_t size) {\n    (void)nmemb;\n    (void)size;\n    insecure_calloc_calls++;\n    return (void*)0xdeadc0de;\n}\n\nstatic void *hi_realloc_fail(void *ptr, size_t size) {\n    (void)ptr;\n    (void)size;\n    return NULL;\n}\n\nstatic void test_allocator_injection(void) {\n    void *ptr;\n\n    hiredisAllocFuncs ha = {\n        .mallocFn = hi_malloc_fail,\n        .callocFn = hi_calloc_fail,\n        .reallocFn = hi_realloc_fail,\n        .strdupFn = strdup,\n        .freeFn = free,\n    };\n\n    // Override hiredis allocators\n    hiredisSetAllocators(&ha);\n\n    test(\"redisContext uses injected allocators: \");\n    redisContext *c = redisConnect(\"localhost\", 6379);\n    test_cond(c == NULL);\n\n    test(\"redisReader uses injected allocators: \");\n    redisReader *reader = redisReaderCreate();\n    test_cond(reader == NULL);\n\n    /* Make sure hiredis itself protects against a non-overflow checking calloc */\n    test(\"hiredis calloc wrapper protects against overflow: \");\n    ha.callocFn = hi_calloc_insecure;\n    hiredisSetAllocators(&ha);\n    ptr = hi_calloc((SIZE_MAX / sizeof(void*)) + 3, sizeof(void*));\n    test_cond(ptr == NULL && insecure_calloc_calls == 0);\n\n    // Return allocators to default\n    hiredisResetAllocators();\n}\n\n#define HIREDIS_BAD_DOMAIN \"idontexist-noreally.com\"\nstatic void test_blocking_connection_errors(void) {\n    struct addrinfo hints = {.ai_family = AF_INET};\n    struct addrinfo *ai_tmp = NULL;\n    redisContext *c;\n\n    int rv = getaddrinfo(HIREDIS_BAD_DOMAIN, \"6379\", &hints, &ai_tmp);\n    if (rv != 0) {\n        // Address does *not* exist\n        test(\"Returns error when host cannot be resolved: \");\n        // First see if this domain name *actually* resolves to NXDOMAIN\n        c = redisConnect(HIREDIS_BAD_DOMAIN, 6379);\n        test_cond(\n            c->err == REDIS_ERR_OTHER &&\n            (strcmp(c->errstr, \"Name or service not known\") == 0 ||\n             strcmp(c->errstr, \"Can't resolve: \" HIREDIS_BAD_DOMAIN) == 0 ||\n             strcmp(c->errstr, \"Name does not resolve\") == 0 ||\n             strcmp(c->errstr, \"nodename nor servname provided, or not known\") == 0 ||\n             strcmp(c->errstr, \"node name or service name not known\") == 0 ||\n             strcmp(c->errstr, \"No address associated with hostname\") == 0 ||\n             strcmp(c->errstr, \"Temporary failure in name resolution\") == 0 ||\n             strcmp(c->errstr, \"hostname nor servname provided, or not known\") == 0 ||\n             strcmp(c->errstr, \"no address associated with name\") == 0 ||\n             strcmp(c->errstr, \"No such host is known. \") == 0));\n        redisFree(c);\n    } else {\n        printf(\"Skipping NXDOMAIN test. Found evil ISP!\\n\");\n        freeaddrinfo(ai_tmp);\n    }\n\n#ifndef _WIN32\n    redisOptions opt = {0};\n    struct timeval tv;\n\n    test(\"Returns error when the port is not open: \");\n    c = redisConnect((char*)\"localhost\", 1);\n    test_cond(c->err == REDIS_ERR_IO &&\n        strcmp(c->errstr,\"Connection refused\") == 0);\n    redisFree(c);\n\n\n    /* Verify we don't regress from the fix in PR #1180 */\n    test(\"We don't clobber connection exception with setsockopt error: \");\n    tv = (struct timeval){.tv_sec = 0, .tv_usec = 500000};\n    opt.command_timeout = opt.connect_timeout = &tv;\n    REDIS_OPTIONS_SET_TCP(&opt, \"localhost\", 10337);\n    c = redisConnectWithOptions(&opt);\n    test_cond(c->err == REDIS_ERR_IO &&\n              strcmp(c->errstr, \"Connection refused\") == 0);\n    redisFree(c);\n\n    test(\"Returns error when the unix_sock socket path doesn't accept connections: \");\n    c = redisConnectUnix((char*)\"/tmp/idontexist.sock\");\n    test_cond(c->err == REDIS_ERR_IO); /* Don't care about the message... */\n    redisFree(c);\n#endif\n}\n\n/* Test push handler */\nvoid push_handler(void *privdata, void *r) {\n    struct pushCounters *pcounts = privdata;\n    redisReply *reply = r, *payload;\n\n    assert(reply && reply->type == REDIS_REPLY_PUSH && reply->elements == 2);\n\n    payload = reply->element[1];\n    if (payload->type == REDIS_REPLY_ARRAY) {\n        payload = payload->element[0];\n    }\n\n    if (payload->type == REDIS_REPLY_STRING) {\n        pcounts->str++;\n    } else if (payload->type == REDIS_REPLY_NIL) {\n        pcounts->nil++;\n    }\n\n    freeReplyObject(reply);\n}\n\n/* Dummy function just to test setting a callback with redisOptions */\nvoid push_handler_async(redisAsyncContext *ac, void *reply) {\n    (void)ac;\n    (void)reply;\n}\n\nstatic void test_resp3_push_handler(redisContext *c) {\n    struct pushCounters pc = {0};\n    redisPushFn *old = NULL;\n    redisReply *reply;\n    void *privdata;\n\n    /* Switch to RESP3 and turn on client tracking */\n    send_hello(c, 3);\n    send_client_tracking(c, \"ON\");\n    privdata = c->privdata;\n    c->privdata = &pc;\n\n    reply = redisCommand(c, \"GET key:0\");\n    assert(reply != NULL);\n    freeReplyObject(reply);\n\n    test(\"RESP3 PUSH messages are handled out of band by default: \");\n    reply = redisCommand(c, \"SET key:0 val:0\");\n    test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS);\n    freeReplyObject(reply);\n\n    assert((reply = redisCommand(c, \"GET key:0\")) != NULL);\n    freeReplyObject(reply);\n\n    old = redisSetPushCallback(c, push_handler);\n    test(\"We can set a custom RESP3 PUSH handler: \");\n    reply = redisCommand(c, \"SET key:0 val:0\");\n    /* We need another command because depending on the version of Redis, the\n     * notification may be delivered after the command's reply. */\n    assert(reply != NULL);\n    freeReplyObject(reply);\n    reply = redisCommand(c, \"PING\");\n    test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS && pc.str == 1);\n    freeReplyObject(reply);\n\n    test(\"We properly handle a NIL invalidation payload: \");\n    reply = redisCommand(c, \"FLUSHDB\");\n    assert(reply != NULL);\n    freeReplyObject(reply);\n    reply = redisCommand(c, \"PING\");\n    test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS && pc.nil == 1);\n    freeReplyObject(reply);\n\n    /* Unset the push callback and generate an invalidate message making\n     * sure it is not handled out of band. */\n    test(\"With no handler, PUSH replies come in-band: \");\n    redisSetPushCallback(c, NULL);\n    assert((reply = redisCommand(c, \"GET key:0\")) != NULL);\n    freeReplyObject(reply);\n    assert((reply = redisCommand(c, \"SET key:0 invalid\")) != NULL);\n    /* Depending on Redis version, we may receive either push notification or\n     * status reply. Both cases are valid. */\n    if (reply->type == REDIS_REPLY_STATUS) {\n        freeReplyObject(reply);\n        reply = redisCommand(c, \"PING\");\n    }\n    test_cond(reply->type == REDIS_REPLY_PUSH);\n    freeReplyObject(reply);\n\n    test(\"With no PUSH handler, no replies are lost: \");\n    assert(redisGetReply(c, (void**)&reply) == REDIS_OK);\n    test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS);\n    freeReplyObject(reply);\n\n    /* Return to the originally set PUSH handler */\n    assert(old != NULL);\n    redisSetPushCallback(c, old);\n\n    /* Switch back to RESP2 and disable tracking */\n    c->privdata = privdata;\n    send_client_tracking(c, \"OFF\");\n    send_hello(c, 2);\n}\n\nredisOptions get_redis_tcp_options(struct config config) {\n    redisOptions options = {0};\n    REDIS_OPTIONS_SET_TCP(&options, config.tcp.host, config.tcp.port);\n    return options;\n}\n\nstatic void test_resp3_push_options(struct config config) {\n    redisAsyncContext *ac;\n    redisContext *c;\n    redisOptions options;\n\n    test(\"We set a default RESP3 handler for redisContext: \");\n    options = get_redis_tcp_options(config);\n    assert((c = redisConnectWithOptions(&options)) != NULL);\n    test_cond(c->push_cb != NULL);\n    redisFree(c);\n\n    test(\"We don't set a default RESP3 push handler for redisAsyncContext: \");\n    options = get_redis_tcp_options(config);\n    assert((ac = redisAsyncConnectWithOptions(&options)) != NULL);\n    test_cond(ac->c.push_cb == NULL);\n    redisAsyncFree(ac);\n\n    test(\"Our REDIS_OPT_NO_PUSH_AUTOFREE flag works: \");\n    options = get_redis_tcp_options(config);\n    options.options |= REDIS_OPT_NO_PUSH_AUTOFREE;\n    assert((c = redisConnectWithOptions(&options)) != NULL);\n    test_cond(c->push_cb == NULL);\n    redisFree(c);\n\n    test(\"We can use redisOptions to set a custom PUSH handler for redisContext: \");\n    options = get_redis_tcp_options(config);\n    options.push_cb = push_handler;\n    assert((c = redisConnectWithOptions(&options)) != NULL);\n    test_cond(c->push_cb == push_handler);\n    redisFree(c);\n\n    test(\"We can use redisOptions to set a custom PUSH handler for redisAsyncContext: \");\n    options = get_redis_tcp_options(config);\n    options.async_push_cb = push_handler_async;\n    assert((ac = redisAsyncConnectWithOptions(&options)) != NULL);\n    test_cond(ac->push_cb == push_handler_async);\n    redisAsyncFree(ac);\n}\n\nvoid free_privdata(void *privdata) {\n    struct privdata *data = privdata;\n    data->dtor_counter++;\n}\n\nstatic void test_privdata_hooks(struct config config) {\n    struct privdata data = {0};\n    redisOptions options;\n    redisContext *c;\n\n    test(\"We can use redisOptions to set privdata: \");\n    options = get_redis_tcp_options(config);\n    REDIS_OPTIONS_SET_PRIVDATA(&options, &data, free_privdata);\n    assert((c = redisConnectWithOptions(&options)) != NULL);\n    test_cond(c->privdata == &data);\n\n    test(\"Our privdata destructor fires when we free the context: \");\n    redisFree(c);\n    test_cond(data.dtor_counter == 1);\n}\n\nstatic void test_blocking_connection(struct config config) {\n    redisContext *c;\n    redisReply *reply;\n    int major;\n\n    c = do_connect(config);\n\n    test(\"Is able to deliver commands: \");\n    reply = redisCommand(c,\"PING\");\n    test_cond(reply->type == REDIS_REPLY_STATUS &&\n        strcasecmp(reply->str,\"pong\") == 0)\n    freeReplyObject(reply);\n\n    test(\"Is a able to send commands verbatim: \");\n    reply = redisCommand(c,\"SET foo bar\");\n    test_cond (reply->type == REDIS_REPLY_STATUS &&\n        strcasecmp(reply->str,\"ok\") == 0)\n    freeReplyObject(reply);\n\n    test(\"%%s String interpolation works: \");\n    reply = redisCommand(c,\"SET %s %s\",\"foo\",\"hello world\");\n    freeReplyObject(reply);\n    reply = redisCommand(c,\"GET foo\");\n    test_cond(reply->type == REDIS_REPLY_STRING &&\n        strcmp(reply->str,\"hello world\") == 0);\n    freeReplyObject(reply);\n\n    test(\"%%b String interpolation works: \");\n    reply = redisCommand(c,\"SET %b %b\",\"foo\",(size_t)3,\"hello\\x00world\",(size_t)11);\n    freeReplyObject(reply);\n    reply = redisCommand(c,\"GET foo\");\n    test_cond(reply->type == REDIS_REPLY_STRING &&\n        memcmp(reply->str,\"hello\\x00world\",11) == 0)\n\n    test(\"Binary reply length is correct: \");\n    test_cond(reply->len == 11)\n    freeReplyObject(reply);\n\n    test(\"Can parse nil replies: \");\n    reply = redisCommand(c,\"GET nokey\");\n    test_cond(reply->type == REDIS_REPLY_NIL)\n    freeReplyObject(reply);\n\n    /* test 7 */\n    test(\"Can parse integer replies: \");\n    reply = redisCommand(c,\"INCR mycounter\");\n    test_cond(reply->type == REDIS_REPLY_INTEGER && reply->integer == 1)\n    freeReplyObject(reply);\n\n    test(\"Can parse multi bulk replies: \");\n    freeReplyObject(redisCommand(c,\"LPUSH mylist foo\"));\n    freeReplyObject(redisCommand(c,\"LPUSH mylist bar\"));\n    reply = redisCommand(c,\"LRANGE mylist 0 -1\");\n    test_cond(reply->type == REDIS_REPLY_ARRAY &&\n              reply->elements == 2 &&\n              !memcmp(reply->element[0]->str,\"bar\",3) &&\n              !memcmp(reply->element[1]->str,\"foo\",3))\n    freeReplyObject(reply);\n\n    /* m/e with multi bulk reply *before* other reply.\n     * specifically test ordering of reply items to parse. */\n    test(\"Can handle nested multi bulk replies: \");\n    freeReplyObject(redisCommand(c,\"MULTI\"));\n    freeReplyObject(redisCommand(c,\"LRANGE mylist 0 -1\"));\n    freeReplyObject(redisCommand(c,\"PING\"));\n    reply = (redisCommand(c,\"EXEC\"));\n    test_cond(reply->type == REDIS_REPLY_ARRAY &&\n              reply->elements == 2 &&\n              reply->element[0]->type == REDIS_REPLY_ARRAY &&\n              reply->element[0]->elements == 2 &&\n              !memcmp(reply->element[0]->element[0]->str,\"bar\",3) &&\n              !memcmp(reply->element[0]->element[1]->str,\"foo\",3) &&\n              reply->element[1]->type == REDIS_REPLY_STATUS &&\n              strcasecmp(reply->element[1]->str,\"pong\") == 0);\n    freeReplyObject(reply);\n\n    test(\"Send command by passing argc/argv: \");\n    const char *argv[3] = {\"SET\", \"foo\", \"bar\"};\n    size_t argvlen[3] = {3, 3, 3};\n    reply = redisCommandArgv(c,3,argv,argvlen);\n    test_cond(reply->type == REDIS_REPLY_STATUS);\n    freeReplyObject(reply);\n\n    /* Make sure passing NULL to redisGetReply is safe */\n    test(\"Can pass NULL to redisGetReply: \");\n    assert(redisAppendCommand(c, \"PING\") == REDIS_OK);\n    test_cond(redisGetReply(c, NULL) == REDIS_OK);\n\n    get_redis_version(c, &major, NULL);\n    if (major >= 6) test_resp3_push_handler(c);\n    test_resp3_push_options(config);\n\n    test_privdata_hooks(config);\n\n    disconnect(c, 0);\n}\n\n/* Send DEBUG SLEEP 0 to detect if we have this command */\nstatic int detect_debug_sleep(redisContext *c) {\n    int detected;\n    redisReply *reply = redisCommand(c, \"DEBUG SLEEP 0\\r\\n\");\n\n    if (reply == NULL || c->err) {\n        const char *cause = c->err ? c->errstr : \"(none)\";\n        fprintf(stderr, \"Error testing for DEBUG SLEEP (Redis error: %s), exiting\\n\", cause);\n        exit(-1);\n    }\n\n    detected = reply->type == REDIS_REPLY_STATUS;\n    freeReplyObject(reply);\n\n    return detected;\n}\n\nstatic void test_blocking_connection_timeouts(struct config config) {\n    redisContext *c;\n    redisReply *reply;\n    ssize_t s;\n    const char *sleep_cmd = \"DEBUG SLEEP 1\\r\\n\";\n    struct timeval tv = {.tv_sec = 0, .tv_usec = 10000};\n\n    c = do_connect(config);\n    test(\"Successfully completes a command when the timeout is not exceeded: \");\n    reply = redisCommand(c,\"SET foo fast\");\n    freeReplyObject(reply);\n    redisSetTimeout(c, tv);\n    reply = redisCommand(c, \"GET foo\");\n    test_cond(reply != NULL && reply->type == REDIS_REPLY_STRING && memcmp(reply->str, \"fast\", 4) == 0);\n    freeReplyObject(reply);\n    disconnect(c, 0);\n\n    c = do_connect(config);\n    test(\"Does not return a reply when the command times out: \");\n    if (detect_debug_sleep(c)) {\n        redisAppendFormattedCommand(c, sleep_cmd, strlen(sleep_cmd));\n\n        // flush connection buffer without waiting for the reply\n        s = c->funcs->write(c);\n        assert(s == (ssize_t)sdslen(c->obuf));\n        sdsfree(c->obuf);\n        c->obuf = sdsempty();\n\n        redisSetTimeout(c, tv);\n        reply = redisCommand(c, \"GET foo\");\n#ifndef _WIN32\n        test_cond(s > 0 && reply == NULL && c->err == REDIS_ERR_IO &&\n                  strcmp(c->errstr, \"Resource temporarily unavailable\") == 0);\n#else\n        test_cond(s > 0 && reply == NULL && c->err == REDIS_ERR_TIMEOUT &&\n                  strcmp(c->errstr, \"recv timeout\") == 0);\n#endif\n        freeReplyObject(reply);\n\n        // wait for the DEBUG SLEEP to complete so that Redis server is unblocked for the following tests\n        millisleep(1100);\n    } else {\n        test_skipped();\n    }\n\n    test(\"Reconnect properly reconnects after a timeout: \");\n    do_reconnect(c, config);\n    reply = redisCommand(c, \"PING\");\n    test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS && strcmp(reply->str, \"PONG\") == 0);\n    freeReplyObject(reply);\n\n    test(\"Reconnect properly uses owned parameters: \");\n    config.tcp.host = \"foo\";\n    config.unix_sock.path = \"foo\";\n    do_reconnect(c, config);\n    reply = redisCommand(c, \"PING\");\n    test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS && strcmp(reply->str, \"PONG\") == 0);\n    freeReplyObject(reply);\n\n    disconnect(c, 0);\n}\n\nstatic void test_blocking_io_errors(struct config config) {\n    redisContext *c;\n    redisReply *reply;\n    void *_reply;\n    int major, minor;\n\n    /* Connect to target given by config. */\n    c = do_connect(config);\n    get_redis_version(c, &major, &minor);\n\n    test(\"Returns I/O error when the connection is lost: \");\n    reply = redisCommand(c,\"QUIT\");\n    if (major > 2 || (major == 2 && minor > 0)) {\n        /* > 2.0 returns OK on QUIT and read() should be issued once more\n         * to know the descriptor is at EOF. */\n        test_cond(strcasecmp(reply->str,\"OK\") == 0 &&\n            redisGetReply(c,&_reply) == REDIS_ERR);\n        freeReplyObject(reply);\n    } else {\n        test_cond(reply == NULL);\n    }\n\n#ifndef _WIN32\n    /* On 2.0, QUIT will cause the connection to be closed immediately and\n     * the read(2) for the reply on QUIT will set the error to EOF.\n     * On >2.0, QUIT will return with OK and another read(2) needed to be\n     * issued to find out the socket was closed by the server. In both\n     * conditions, the error will be set to EOF. */\n    assert(c->err == REDIS_ERR_EOF &&\n        strcmp(c->errstr,\"Server closed the connection\") == 0);\n#endif\n    redisFree(c);\n\n    c = do_connect(config);\n    test(\"Returns I/O error on socket timeout: \");\n    struct timeval tv = { 0, 1000 };\n    assert(redisSetTimeout(c,tv) == REDIS_OK);\n    int respcode = redisGetReply(c,&_reply);\n#ifndef _WIN32\n    test_cond(respcode == REDIS_ERR && c->err == REDIS_ERR_IO && errno == EAGAIN);\n#else\n    test_cond(respcode == REDIS_ERR && c->err == REDIS_ERR_TIMEOUT);\n#endif\n    redisFree(c);\n}\n\nstatic void test_invalid_timeout_errors(struct config config) {\n    redisContext *c = NULL;\n\n    test(\"Set error when an invalid timeout usec value is used during connect: \");\n\n    config.connect_timeout.tv_sec = 0;\n    config.connect_timeout.tv_usec = 10000001;\n\n    if (config.type == CONN_TCP || config.type == CONN_SSL) {\n        c = redisConnectWithTimeout(config.tcp.host, config.tcp.port, config.connect_timeout);\n    } else if(config.type == CONN_UNIX) {\n        c = redisConnectUnixWithTimeout(config.unix_sock.path, config.connect_timeout);\n    } else {\n        redisTestPanic(\"Unknown connection type!\");\n    }\n\n    test_cond(c != NULL && c->err == REDIS_ERR_IO && strcmp(c->errstr, \"Invalid timeout specified\") == 0);\n    redisFree(c);\n\n    test(\"Set error when an invalid timeout sec value is used during connect: \");\n\n    config.connect_timeout.tv_sec = (((LONG_MAX) - 999) / 1000) + 1;\n    config.connect_timeout.tv_usec = 0;\n\n    if (config.type == CONN_TCP || config.type == CONN_SSL) {\n        c = redisConnectWithTimeout(config.tcp.host, config.tcp.port, config.connect_timeout);\n    } else if(config.type == CONN_UNIX) {\n        c = redisConnectUnixWithTimeout(config.unix_sock.path, config.connect_timeout);\n    } else {\n        redisTestPanic(\"Unknown connection type!\");\n    }\n\n    test_cond(c != NULL && c->err == REDIS_ERR_IO && strcmp(c->errstr, \"Invalid timeout specified\") == 0);\n    redisFree(c);\n}\n\n/* Wrap malloc to abort on failure so OOM checks don't make the test logic\n * harder to follow. */\nvoid *hi_malloc_safe(size_t size) {\n    void *ptr = hi_malloc(size);\n    if (ptr == NULL) {\n        fprintf(stderr, \"Error:  Out of memory\\n\");\n        exit(-1);\n    }\n\n    return ptr;\n}\n\nstatic void test_throughput(struct config config) {\n    redisContext *c = do_connect(config);\n    redisReply **replies;\n    int i, num;\n    long long t1, t2;\n\n    test(\"Throughput:\\n\");\n    for (i = 0; i < 500; i++)\n        freeReplyObject(redisCommand(c,\"LPUSH mylist foo\"));\n\n    num = 1000;\n    replies = hi_malloc_safe(sizeof(redisReply*)*num);\n    t1 = usec();\n    for (i = 0; i < num; i++) {\n        replies[i] = redisCommand(c,\"PING\");\n        assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_STATUS);\n    }\n    t2 = usec();\n    for (i = 0; i < num; i++) freeReplyObject(replies[i]);\n    hi_free(replies);\n    printf(\"\\t(%dx PING: %.3fs)\\n\", num, (t2-t1)/1000000.0);\n\n    replies = hi_malloc_safe(sizeof(redisReply*)*num);\n    t1 = usec();\n    for (i = 0; i < num; i++) {\n        replies[i] = redisCommand(c,\"LRANGE mylist 0 499\");\n        assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_ARRAY);\n        assert(replies[i] != NULL && replies[i]->elements == 500);\n    }\n    t2 = usec();\n    for (i = 0; i < num; i++) freeReplyObject(replies[i]);\n    hi_free(replies);\n    printf(\"\\t(%dx LRANGE with 500 elements: %.3fs)\\n\", num, (t2-t1)/1000000.0);\n\n    replies = hi_malloc_safe(sizeof(redisReply*)*num);\n    t1 = usec();\n    for (i = 0; i < num; i++) {\n        replies[i] = redisCommand(c, \"INCRBY incrkey %d\", 1000000);\n        assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_INTEGER);\n    }\n    t2 = usec();\n    for (i = 0; i < num; i++) freeReplyObject(replies[i]);\n    hi_free(replies);\n    printf(\"\\t(%dx INCRBY: %.3fs)\\n\", num, (t2-t1)/1000000.0);\n\n    num = 10000;\n    replies = hi_malloc_safe(sizeof(redisReply*)*num);\n    for (i = 0; i < num; i++)\n        redisAppendCommand(c,\"PING\");\n    t1 = usec();\n    for (i = 0; i < num; i++) {\n        assert(redisGetReply(c, (void*)&replies[i]) == REDIS_OK);\n        assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_STATUS);\n    }\n    t2 = usec();\n    for (i = 0; i < num; i++) freeReplyObject(replies[i]);\n    hi_free(replies);\n    printf(\"\\t(%dx PING (pipelined): %.3fs)\\n\", num, (t2-t1)/1000000.0);\n\n    replies = hi_malloc_safe(sizeof(redisReply*)*num);\n    for (i = 0; i < num; i++)\n        redisAppendCommand(c,\"LRANGE mylist 0 499\");\n    t1 = usec();\n    for (i = 0; i < num; i++) {\n        assert(redisGetReply(c, (void*)&replies[i]) == REDIS_OK);\n        assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_ARRAY);\n        assert(replies[i] != NULL && replies[i]->elements == 500);\n    }\n    t2 = usec();\n    for (i = 0; i < num; i++) freeReplyObject(replies[i]);\n    hi_free(replies);\n    printf(\"\\t(%dx LRANGE with 500 elements (pipelined): %.3fs)\\n\", num, (t2-t1)/1000000.0);\n\n    replies = hi_malloc_safe(sizeof(redisReply*)*num);\n    for (i = 0; i < num; i++)\n        redisAppendCommand(c,\"INCRBY incrkey %d\", 1000000);\n    t1 = usec();\n    for (i = 0; i < num; i++) {\n        assert(redisGetReply(c, (void*)&replies[i]) == REDIS_OK);\n        assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_INTEGER);\n    }\n    t2 = usec();\n    for (i = 0; i < num; i++) freeReplyObject(replies[i]);\n    hi_free(replies);\n    printf(\"\\t(%dx INCRBY (pipelined): %.3fs)\\n\", num, (t2-t1)/1000000.0);\n\n    disconnect(c, 0);\n}\n\n// static long __test_callback_flags = 0;\n// static void __test_callback(redisContext *c, void *privdata) {\n//     ((void)c);\n//     /* Shift to detect execution order */\n//     __test_callback_flags <<= 8;\n//     __test_callback_flags |= (long)privdata;\n// }\n//\n// static void __test_reply_callback(redisContext *c, redisReply *reply, void *privdata) {\n//     ((void)c);\n//     /* Shift to detect execution order */\n//     __test_callback_flags <<= 8;\n//     __test_callback_flags |= (long)privdata;\n//     if (reply) freeReplyObject(reply);\n// }\n//\n// static redisContext *__connect_nonblock() {\n//     /* Reset callback flags */\n//     __test_callback_flags = 0;\n//     return redisConnectNonBlock(\"127.0.0.1\", port, NULL);\n// }\n//\n// static void test_nonblocking_connection() {\n//     redisContext *c;\n//     int wdone = 0;\n//\n//     test(\"Calls command callback when command is issued: \");\n//     c = __connect_nonblock();\n//     redisSetCommandCallback(c,__test_callback,(void*)1);\n//     redisCommand(c,\"PING\");\n//     test_cond(__test_callback_flags == 1);\n//     redisFree(c);\n//\n//     test(\"Calls disconnect callback on redisDisconnect: \");\n//     c = __connect_nonblock();\n//     redisSetDisconnectCallback(c,__test_callback,(void*)2);\n//     redisDisconnect(c);\n//     test_cond(__test_callback_flags == 2);\n//     redisFree(c);\n//\n//     test(\"Calls disconnect callback and free callback on redisFree: \");\n//     c = __connect_nonblock();\n//     redisSetDisconnectCallback(c,__test_callback,(void*)2);\n//     redisSetFreeCallback(c,__test_callback,(void*)4);\n//     redisFree(c);\n//     test_cond(__test_callback_flags == ((2 << 8) | 4));\n//\n//     test(\"redisBufferWrite against empty write buffer: \");\n//     c = __connect_nonblock();\n//     test_cond(redisBufferWrite(c,&wdone) == REDIS_OK && wdone == 1);\n//     redisFree(c);\n//\n//     test(\"redisBufferWrite against not yet connected fd: \");\n//     c = __connect_nonblock();\n//     redisCommand(c,\"PING\");\n//     test_cond(redisBufferWrite(c,NULL) == REDIS_ERR &&\n//               strncmp(c->error,\"write:\",6) == 0);\n//     redisFree(c);\n//\n//     test(\"redisBufferWrite against closed fd: \");\n//     c = __connect_nonblock();\n//     redisCommand(c,\"PING\");\n//     redisDisconnect(c);\n//     test_cond(redisBufferWrite(c,NULL) == REDIS_ERR &&\n//               strncmp(c->error,\"write:\",6) == 0);\n//     redisFree(c);\n//\n//     test(\"Process callbacks in the right sequence: \");\n//     c = __connect_nonblock();\n//     redisCommandWithCallback(c,__test_reply_callback,(void*)1,\"PING\");\n//     redisCommandWithCallback(c,__test_reply_callback,(void*)2,\"PING\");\n//     redisCommandWithCallback(c,__test_reply_callback,(void*)3,\"PING\");\n//\n//     /* Write output buffer */\n//     wdone = 0;\n//     while(!wdone) {\n//         usleep(500);\n//         redisBufferWrite(c,&wdone);\n//     }\n//\n//     /* Read until at least one callback is executed (the 3 replies will\n//      * arrive in a single packet, causing all callbacks to be executed in\n//      * a single pass). */\n//     while(__test_callback_flags == 0) {\n//         assert(redisBufferRead(c) == REDIS_OK);\n//         redisProcessCallbacks(c);\n//     }\n//     test_cond(__test_callback_flags == 0x010203);\n//     redisFree(c);\n//\n//     test(\"redisDisconnect executes pending callbacks with NULL reply: \");\n//     c = __connect_nonblock();\n//     redisSetDisconnectCallback(c,__test_callback,(void*)1);\n//     redisCommandWithCallback(c,__test_reply_callback,(void*)2,\"PING\");\n//     redisDisconnect(c);\n//     test_cond(__test_callback_flags == 0x0201);\n//     redisFree(c);\n// }\n\n#ifdef HIREDIS_TEST_ASYNC\n\n#pragma GCC diagnostic ignored \"-Woverlength-strings\"   /* required on gcc 4.8.x due to assert statements */\n\nstruct event_base *base;\n\ntypedef struct TestState {\n    redisOptions *options;\n    int           checkpoint;\n    int           resp3;\n    int           disconnect;\n} TestState;\n\n/* Helper to disconnect and stop event loop */\nvoid async_disconnect(redisAsyncContext *ac) {\n    redisAsyncDisconnect(ac);\n    event_base_loopbreak(base);\n}\n\n/* Testcase timeout, will trigger a failure */\nvoid timeout_cb(int fd, short event, void *arg) {\n    (void) fd; (void) event; (void) arg;\n    printf(\"Timeout in async testing!\\n\");\n    exit(1);\n}\n\n/* Unexpected call, will trigger a failure */\nvoid unexpected_cb(redisAsyncContext *ac, void *r, void *privdata) {\n    (void) ac; (void) r;\n    printf(\"Unexpected call: %s\\n\",(char*)privdata);\n    exit(1);\n}\n\n/* Helper function to publish a message via own client. */\nvoid publish_msg(redisOptions *options, const char* channel, const char* msg) {\n    redisContext *c = redisConnectWithOptions(options);\n    assert(c != NULL);\n    redisReply *reply = redisCommand(c,\"PUBLISH %s %s\",channel,msg);\n    assert(reply->type == REDIS_REPLY_INTEGER && reply->integer == 1);\n    freeReplyObject(reply);\n    disconnect(c, 0);\n}\n\n/* Expect a reply of type INTEGER */\nvoid integer_cb(redisAsyncContext *ac, void *r, void *privdata) {\n    redisReply *reply = r;\n    TestState *state = privdata;\n    assert(reply != NULL && reply->type == REDIS_REPLY_INTEGER);\n    state->checkpoint++;\n    if (state->disconnect) async_disconnect(ac);\n}\n\n/* Subscribe callback for test_pubsub_handling and test_pubsub_handling_resp3:\n * - a published message triggers an unsubscribe\n * - a command is sent before the unsubscribe response is received. */\nvoid subscribe_cb(redisAsyncContext *ac, void *r, void *privdata) {\n    redisReply *reply = r;\n    TestState *state = privdata;\n\n    assert(reply != NULL &&\n           reply->type == (state->resp3 ? REDIS_REPLY_PUSH : REDIS_REPLY_ARRAY) &&\n           reply->elements == 3);\n\n    if (strcmp(reply->element[0]->str,\"subscribe\") == 0) {\n        assert(strcmp(reply->element[1]->str,\"mychannel\") == 0 &&\n               reply->element[2]->str == NULL);\n        publish_msg(state->options,\"mychannel\",\"Hello!\");\n    } else if (strcmp(reply->element[0]->str,\"message\") == 0) {\n        assert(strcmp(reply->element[1]->str,\"mychannel\") == 0 &&\n               strcmp(reply->element[2]->str,\"Hello!\") == 0);\n        state->checkpoint++;\n\n        /* Unsubscribe after receiving the published message. Send unsubscribe\n         * which should call the callback registered during subscribe */\n        redisAsyncCommand(ac,unexpected_cb,\n                          (void*)\"unsubscribe should call subscribe_cb()\",\n                          \"unsubscribe\");\n        /* Send a regular command after unsubscribing, then disconnect */\n        state->disconnect = 1;\n        redisAsyncCommand(ac,integer_cb,state,\"LPUSH mylist foo\");\n\n    } else if (strcmp(reply->element[0]->str,\"unsubscribe\") == 0) {\n        assert(strcmp(reply->element[1]->str,\"mychannel\") == 0 &&\n               reply->element[2]->str == NULL);\n    } else {\n        printf(\"Unexpected pubsub command: %s\\n\", reply->element[0]->str);\n        exit(1);\n    }\n}\n\n/* Expect a reply of type ARRAY */\nvoid array_cb(redisAsyncContext *ac, void *r, void *privdata) {\n    redisReply *reply = r;\n    TestState *state = privdata;\n    assert(reply != NULL && reply->type == REDIS_REPLY_ARRAY);\n    state->checkpoint++;\n    if (state->disconnect) async_disconnect(ac);\n}\n\n/* Expect a NULL reply */\nvoid null_cb(redisAsyncContext *ac, void *r, void *privdata) {\n    (void) ac;\n    assert(r == NULL);\n    TestState *state = privdata;\n    state->checkpoint++;\n}\n\nstatic void test_pubsub_handling(struct config config) {\n    test(\"Subscribe, handle published message and unsubscribe: \");\n    /* Setup event dispatcher with a testcase timeout */\n    base = event_base_new();\n    struct event *timeout = evtimer_new(base, timeout_cb, NULL);\n    assert(timeout != NULL);\n\n    evtimer_assign(timeout,base,timeout_cb,NULL);\n    struct timeval timeout_tv = {.tv_sec = 10};\n    evtimer_add(timeout, &timeout_tv);\n\n    /* Connect */\n    redisOptions options = get_redis_tcp_options(config);\n    redisAsyncContext *ac = redisAsyncConnectWithOptions(&options);\n    assert(ac != NULL && ac->err == 0);\n    redisLibeventAttach(ac,base);\n\n    /* Start subscribe */\n    TestState state = {.options = &options};\n    redisAsyncCommand(ac,subscribe_cb,&state,\"subscribe mychannel\");\n\n    /* Make sure non-subscribe commands are handled */\n    redisAsyncCommand(ac,array_cb,&state,\"PING\");\n\n    /* Start event dispatching loop */\n    test_cond(event_base_dispatch(base) == 0);\n    event_free(timeout);\n    event_base_free(base);\n\n    /* Verify test checkpoints */\n    assert(state.checkpoint == 3);\n}\n\n/* Unexpected push message, will trigger a failure */\nvoid unexpected_push_cb(redisAsyncContext *ac, void *r) {\n    (void) ac; (void) r;\n    printf(\"Unexpected call to the PUSH callback!\\n\");\n    exit(1);\n}\n\nstatic void test_pubsub_handling_resp3(struct config config) {\n    test(\"Subscribe, handle published message and unsubscribe using RESP3: \");\n    /* Setup event dispatcher with a testcase timeout */\n    base = event_base_new();\n    struct event *timeout = evtimer_new(base, timeout_cb, NULL);\n    assert(timeout != NULL);\n\n    evtimer_assign(timeout,base,timeout_cb,NULL);\n    struct timeval timeout_tv = {.tv_sec = 10};\n    evtimer_add(timeout, &timeout_tv);\n\n    /* Connect */\n    redisOptions options = get_redis_tcp_options(config);\n    redisAsyncContext *ac = redisAsyncConnectWithOptions(&options);\n    assert(ac != NULL && ac->err == 0);\n    redisLibeventAttach(ac,base);\n\n    /* Not expecting any push messages in this test */\n    redisAsyncSetPushCallback(ac, unexpected_push_cb);\n\n    /* Switch protocol */\n    redisAsyncCommand(ac,NULL,NULL,\"HELLO 3\");\n\n    /* Start subscribe */\n    TestState state = {.options = &options, .resp3 = 1};\n    redisAsyncCommand(ac,subscribe_cb,&state,\"subscribe mychannel\");\n\n    /* Make sure non-subscribe commands are handled in RESP3 */\n    redisAsyncCommand(ac,integer_cb,&state,\"LPUSH mylist foo\");\n    redisAsyncCommand(ac,integer_cb,&state,\"LPUSH mylist foo\");\n    redisAsyncCommand(ac,integer_cb,&state,\"LPUSH mylist foo\");\n    /* Handle an array with 3 elements as a non-subscribe command */\n    redisAsyncCommand(ac,array_cb,&state,\"LRANGE mylist 0 2\");\n\n    /* Start event dispatching loop */\n    test_cond(event_base_dispatch(base) == 0);\n    event_free(timeout);\n    event_base_free(base);\n\n    /* Verify test checkpoints */\n    assert(state.checkpoint == 6);\n}\n\n/* Subscribe callback for test_command_timeout_during_pubsub:\n * - a subscribe response triggers a published message\n * - the published message triggers a command that times out\n * - the command timeout triggers a disconnect */\nvoid subscribe_with_timeout_cb(redisAsyncContext *ac, void *r, void *privdata) {\n    redisReply *reply = r;\n    TestState *state = privdata;\n\n    /* The non-clean disconnect should trigger the\n     * subscription callback with a NULL reply. */\n    if (reply == NULL) {\n        state->checkpoint++;\n        event_base_loopbreak(base);\n        return;\n    }\n\n    assert(reply->type == (state->resp3 ? REDIS_REPLY_PUSH : REDIS_REPLY_ARRAY) &&\n           reply->elements == 3);\n\n    if (strcmp(reply->element[0]->str,\"subscribe\") == 0) {\n        assert(strcmp(reply->element[1]->str,\"mychannel\") == 0 &&\n               reply->element[2]->str == NULL);\n        publish_msg(state->options,\"mychannel\",\"Hello!\");\n        state->checkpoint++;\n    } else if (strcmp(reply->element[0]->str,\"message\") == 0) {\n        assert(strcmp(reply->element[1]->str,\"mychannel\") == 0 &&\n               strcmp(reply->element[2]->str,\"Hello!\") == 0);\n        state->checkpoint++;\n\n        /* Send a command that will trigger a timeout */\n        redisAsyncCommand(ac,null_cb,state,\"DEBUG SLEEP 3\");\n        redisAsyncCommand(ac,null_cb,state,\"LPUSH mylist foo\");\n    } else {\n        printf(\"Unexpected pubsub command: %s\\n\", reply->element[0]->str);\n        exit(1);\n    }\n}\n\nstatic void test_command_timeout_during_pubsub(struct config config) {\n    test(\"Command timeout during Pub/Sub: \");\n    /* Setup event dispatcher with a testcase timeout */\n    base = event_base_new();\n    struct event *timeout = evtimer_new(base,timeout_cb,NULL);\n    assert(timeout != NULL);\n\n    evtimer_assign(timeout,base,timeout_cb,NULL);\n    struct timeval timeout_tv = {.tv_sec = 10};\n    evtimer_add(timeout,&timeout_tv);\n\n    /* Connect */\n    redisOptions options = get_redis_tcp_options(config);\n    redisAsyncContext *ac = redisAsyncConnectWithOptions(&options);\n    assert(ac != NULL && ac->err == 0);\n    redisLibeventAttach(ac,base);\n\n    /* Configure a command timout */\n    struct timeval command_timeout = {.tv_sec = 2};\n    redisAsyncSetTimeout(ac,command_timeout);\n\n    /* Not expecting any push messages in this test */\n    redisAsyncSetPushCallback(ac,unexpected_push_cb);\n\n    /* Switch protocol */\n    redisAsyncCommand(ac,NULL,NULL,\"HELLO 3\");\n\n    /* Start subscribe */\n    TestState state = {.options = &options, .resp3 = 1};\n    redisAsyncCommand(ac,subscribe_with_timeout_cb,&state,\"subscribe mychannel\");\n\n    /* Start event dispatching loop */\n    assert(event_base_dispatch(base) == 0);\n    event_free(timeout);\n    event_base_free(base);\n\n    /* Verify test checkpoints */\n    test_cond(state.checkpoint == 5);\n}\n\n/* Subscribe callback for test_pubsub_multiple_channels */\nvoid subscribe_channel_a_cb(redisAsyncContext *ac, void *r, void *privdata) {\n    redisReply *reply = r;\n    TestState *state = privdata;\n\n    assert(reply != NULL && reply->type == REDIS_REPLY_ARRAY &&\n           reply->elements == 3);\n\n    if (strcmp(reply->element[0]->str,\"subscribe\") == 0) {\n        assert(strcmp(reply->element[1]->str,\"A\") == 0);\n        publish_msg(state->options,\"A\",\"Hello!\");\n        state->checkpoint++;\n    } else if (strcmp(reply->element[0]->str,\"message\") == 0) {\n        assert(strcmp(reply->element[1]->str,\"A\") == 0 &&\n               strcmp(reply->element[2]->str,\"Hello!\") == 0);\n        state->checkpoint++;\n\n        /* Unsubscribe to channels, including channel X & Z which we don't subscribe to */\n        redisAsyncCommand(ac,unexpected_cb,\n                          (void*)\"unsubscribe should not call unexpected_cb()\",\n                          \"unsubscribe B X A A Z\");\n        /* Unsubscribe to patterns, none which we subscribe to */\n        redisAsyncCommand(ac,unexpected_cb,\n                          (void*)\"punsubscribe should not call unexpected_cb()\",\n                          \"punsubscribe\");\n        /* Send a regular command after unsubscribing, then disconnect */\n        state->disconnect = 1;\n        redisAsyncCommand(ac,integer_cb,state,\"LPUSH mylist foo\");\n    } else if (strcmp(reply->element[0]->str,\"unsubscribe\") == 0) {\n        assert(strcmp(reply->element[1]->str,\"A\") == 0);\n        state->checkpoint++;\n    } else {\n        printf(\"Unexpected pubsub command: %s\\n\", reply->element[0]->str);\n        exit(1);\n    }\n}\n\n/* Subscribe callback for test_pubsub_multiple_channels */\nvoid subscribe_channel_b_cb(redisAsyncContext *ac, void *r, void *privdata) {\n    redisReply *reply = r;\n    TestState *state = privdata;\n    (void)ac;\n\n    assert(reply != NULL && reply->type == REDIS_REPLY_ARRAY &&\n           reply->elements == 3);\n\n    if (strcmp(reply->element[0]->str,\"subscribe\") == 0) {\n        assert(strcmp(reply->element[1]->str,\"B\") == 0);\n        state->checkpoint++;\n    } else if (strcmp(reply->element[0]->str,\"unsubscribe\") == 0) {\n        assert(strcmp(reply->element[1]->str,\"B\") == 0);\n        state->checkpoint++;\n    } else {\n        printf(\"Unexpected pubsub command: %s\\n\", reply->element[0]->str);\n        exit(1);\n    }\n}\n\n/* Test handling of multiple channels\n * - subscribe to channel A and B\n * - a published message on A triggers an unsubscribe of channel B, X, A and Z\n *   where channel X and Z are not subscribed to.\n * - the published message also triggers an unsubscribe to patterns. Since no\n *   pattern is subscribed to the responded pattern element type is NIL.\n * - a command sent after unsubscribe triggers a disconnect */\nstatic void test_pubsub_multiple_channels(struct config config) {\n    test(\"Subscribe to multiple channels: \");\n    /* Setup event dispatcher with a testcase timeout */\n    base = event_base_new();\n    struct event *timeout = evtimer_new(base,timeout_cb,NULL);\n    assert(timeout != NULL);\n\n    evtimer_assign(timeout,base,timeout_cb,NULL);\n    struct timeval timeout_tv = {.tv_sec = 10};\n    evtimer_add(timeout,&timeout_tv);\n\n    /* Connect */\n    redisOptions options = get_redis_tcp_options(config);\n    redisAsyncContext *ac = redisAsyncConnectWithOptions(&options);\n    assert(ac != NULL && ac->err == 0);\n    redisLibeventAttach(ac,base);\n\n    /* Not expecting any push messages in this test */\n    redisAsyncSetPushCallback(ac,unexpected_push_cb);\n\n    /* Start subscribing to two channels */\n    TestState state = {.options = &options};\n    redisAsyncCommand(ac,subscribe_channel_a_cb,&state,\"subscribe A\");\n    redisAsyncCommand(ac,subscribe_channel_b_cb,&state,\"subscribe B\");\n\n    /* Start event dispatching loop */\n    assert(event_base_dispatch(base) == 0);\n    event_free(timeout);\n    event_base_free(base);\n\n    /* Verify test checkpoints */\n    test_cond(state.checkpoint == 6);\n}\n\n/* Command callback for test_monitor() */\nvoid monitor_cb(redisAsyncContext *ac, void *r, void *privdata) {\n    redisReply *reply = r;\n    TestState *state = privdata;\n\n    /* NULL reply is received when BYE triggers a disconnect. */\n    if (reply == NULL) {\n        event_base_loopbreak(base);\n        return;\n    }\n\n    assert(reply != NULL && reply->type == REDIS_REPLY_STATUS);\n    state->checkpoint++;\n\n    if (state->checkpoint == 1) {\n        /* Response from MONITOR */\n        redisContext *c = redisConnectWithOptions(state->options);\n        assert(c != NULL);\n        redisReply *reply = redisCommand(c,\"SET first 1\");\n        assert(reply->type == REDIS_REPLY_STATUS);\n        freeReplyObject(reply);\n        redisFree(c);\n    } else if (state->checkpoint == 2) {\n        /* Response for monitored command 'SET first 1' */\n        assert(strstr(reply->str,\"first\") != NULL);\n        redisContext *c = redisConnectWithOptions(state->options);\n        assert(c != NULL);\n        redisReply *reply = redisCommand(c,\"SET second 2\");\n        assert(reply->type == REDIS_REPLY_STATUS);\n        freeReplyObject(reply);\n        redisFree(c);\n    } else if (state->checkpoint == 3) {\n        /* Response for monitored command 'SET second 2' */\n        assert(strstr(reply->str,\"second\") != NULL);\n        /* Send QUIT to disconnect */\n        redisAsyncCommand(ac,NULL,NULL,\"QUIT\");\n    }\n}\n\n/* Test handling of the monitor command\n * - sends MONITOR to enable monitoring.\n * - sends SET commands via separate clients to be monitored.\n * - sends QUIT to stop monitoring and disconnect. */\nstatic void test_monitor(struct config config) {\n    test(\"Enable monitoring: \");\n    /* Setup event dispatcher with a testcase timeout */\n    base = event_base_new();\n    struct event *timeout = evtimer_new(base, timeout_cb, NULL);\n    assert(timeout != NULL);\n\n    evtimer_assign(timeout,base,timeout_cb,NULL);\n    struct timeval timeout_tv = {.tv_sec = 10};\n    evtimer_add(timeout, &timeout_tv);\n\n    /* Connect */\n    redisOptions options = get_redis_tcp_options(config);\n    redisAsyncContext *ac = redisAsyncConnectWithOptions(&options);\n    assert(ac != NULL && ac->err == 0);\n    redisLibeventAttach(ac,base);\n\n    /* Not expecting any push messages in this test */\n    redisAsyncSetPushCallback(ac,unexpected_push_cb);\n\n    /* Start monitor */\n    TestState state = {.options = &options};\n    redisAsyncCommand(ac,monitor_cb,&state,\"monitor\");\n\n    /* Start event dispatching loop */\n    test_cond(event_base_dispatch(base) == 0);\n    event_free(timeout);\n    event_base_free(base);\n\n    /* Verify test checkpoints */\n    assert(state.checkpoint == 3);\n}\n#endif /* HIREDIS_TEST_ASYNC */\n\n/* tests for async api using polling adapter, requires no extra libraries*/\n\n/* enum for the test cases, the callbacks have different logic based on them */\ntypedef enum astest_no\n{\n    ASTEST_CONNECT=0,\n    ASTEST_CONN_TIMEOUT,\n    ASTEST_PINGPONG,\n    ASTEST_PINGPONG_TIMEOUT,\n    ASTEST_ISSUE_931,\n    ASTEST_ISSUE_931_PING\n}astest_no;\n\n/* a static context for the async tests */\nstruct _astest {\n    redisAsyncContext *ac;\n    astest_no testno;\n    int counter;\n    int connects;\n    int connect_status;\n    int disconnects;\n    int pongs;\n    int disconnect_status;\n    int connected;\n    int err;\n    char errstr[256];\n};\nstatic struct _astest astest;\n\n/* async callbacks */\nstatic void asCleanup(void* data)\n{\n    struct _astest *t = (struct _astest *)data;\n    t->ac = NULL;\n}\n\nstatic void commandCallback(struct redisAsyncContext *ac, void* _reply, void* _privdata);\n\nstatic void connectCallback(redisAsyncContext *c, int status) {\n    struct _astest *t = (struct _astest *)c->data;\n    assert(t == &astest);\n    assert(t->connects == 0);\n    t->err = c->err;\n    strcpy(t->errstr, c->errstr);\n    t->connects++;\n    t->connect_status = status;\n    t->connected = status == REDIS_OK ? 1 : -1;\n\n    if (t->testno == ASTEST_ISSUE_931) {\n        /* disconnect again */\n        redisAsyncDisconnect(c);\n    }\n    else if (t->testno == ASTEST_ISSUE_931_PING)\n    {\n        redisAsyncCommand(c, commandCallback, NULL, \"PING\");\n    }\n}\nstatic void disconnectCallback(const redisAsyncContext *c, int status) {\n    assert(c->data == (void*)&astest);\n    assert(astest.disconnects == 0);\n    astest.err = c->err;\n    strcpy(astest.errstr, c->errstr);\n    astest.disconnects++;\n    astest.disconnect_status = status;\n    astest.connected = 0;\n}\n\nstatic void commandCallback(struct redisAsyncContext *ac, void* _reply, void* _privdata)\n{\n    redisReply *reply = (redisReply*)_reply;\n    struct _astest *t = (struct _astest *)ac->data;\n    assert(t == &astest);\n    (void)_privdata;\n    t->err = ac->err;\n    strcpy(t->errstr, ac->errstr);\n    t->counter++;\n    if (t->testno == ASTEST_PINGPONG ||t->testno == ASTEST_ISSUE_931_PING)\n    {\n        assert(reply != NULL && reply->type == REDIS_REPLY_STATUS && strcmp(reply->str, \"PONG\") == 0);\n        t->pongs++;\n        redisAsyncFree(ac);\n    }\n    if (t->testno == ASTEST_PINGPONG_TIMEOUT)\n    {\n        /* two ping pongs */\n        assert(reply != NULL && reply->type == REDIS_REPLY_STATUS && strcmp(reply->str, \"PONG\") == 0);\n        t->pongs++;\n        if (t->counter == 1) {\n            int status = redisAsyncCommand(ac, commandCallback, NULL, \"PING\");\n            assert(status == REDIS_OK);\n        } else {\n            redisAsyncFree(ac);\n        }\n    }\n}\n\nstatic redisAsyncContext *do_aconnect(struct config config, astest_no testno)\n{\n    redisOptions options = {0};\n    memset(&astest, 0, sizeof(astest));\n\n    astest.testno = testno;\n    astest.connect_status = astest.disconnect_status = -2;\n\n    if (config.type == CONN_TCP) {\n        options.type = REDIS_CONN_TCP;\n        options.connect_timeout = &config.connect_timeout;\n        REDIS_OPTIONS_SET_TCP(&options, config.tcp.host, config.tcp.port);\n    } else if (config.type == CONN_SSL) {\n        options.type = REDIS_CONN_TCP;\n        options.connect_timeout = &config.connect_timeout;\n        REDIS_OPTIONS_SET_TCP(&options, config.ssl.host, config.ssl.port);\n    } else if (config.type == CONN_UNIX) {\n        options.type = REDIS_CONN_UNIX;\n        options.endpoint.unix_socket = config.unix_sock.path;\n    } else if (config.type == CONN_FD) {\n        options.type = REDIS_CONN_USERFD;\n        /* Create a dummy connection just to get an fd to inherit */\n        redisContext *dummy_ctx = redisConnectUnix(config.unix_sock.path);\n        if (dummy_ctx) {\n            redisFD fd = disconnect(dummy_ctx, 1);\n            printf(\"Connecting to inherited fd %d\\n\", (int)fd);\n            options.endpoint.fd = fd;\n        }\n    }\n    redisAsyncContext *c = redisAsyncConnectWithOptions(&options);\n    assert(c);\n    astest.ac = c;\n    c->data = &astest;\n    c->dataCleanup = asCleanup;\n    redisPollAttach(c);\n    redisAsyncSetConnectCallbackNC(c, connectCallback);\n    redisAsyncSetDisconnectCallback(c, disconnectCallback);\n    return c;\n}\n\nstatic void as_printerr(void) {\n    printf(\"Async err %d : %s\\n\", astest.err, astest.errstr);\n}\n\n#define ASASSERT(e) do { \\\n    if (!(e)) \\\n        as_printerr(); \\\n    assert(e); \\\n} while (0);\n\nstatic void test_async_polling(struct config config) {\n    int status;\n    redisAsyncContext *c;\n    struct config defaultconfig = config;\n\n    test(\"Async connect: \");\n    c = do_aconnect(config, ASTEST_CONNECT);\n    assert(c);\n    while(astest.connected == 0)\n        redisPollTick(c, 0.1);\n    assert(astest.connects == 1);\n    ASASSERT(astest.connect_status == REDIS_OK);\n    assert(astest.disconnects == 0);\n    test_cond(astest.connected == 1);\n\n    test(\"Async free after connect: \");\n    assert(astest.ac != NULL);\n    redisAsyncFree(c);\n    assert(astest.disconnects == 1);\n    assert(astest.ac == NULL);\n    test_cond(astest.disconnect_status == REDIS_OK);\n\n    if (config.type == CONN_TCP || config.type == CONN_SSL) {\n        /* timeout can only be simulated with network */\n        test(\"Async connect timeout: \");\n        config.tcp.host = \"192.168.254.254\";  /* blackhole ip */\n        config.connect_timeout.tv_usec = 100000;\n        c = do_aconnect(config, ASTEST_CONN_TIMEOUT);\n        assert(c);\n        assert(c->err == 0);\n        while(astest.connected == 0)\n            redisPollTick(c, 0.1);\n        assert(astest.connected == -1);\n        /*\n         * freeing should not be done, clearing should have happened.\n         *redisAsyncFree(c);\n         */\n        assert(astest.ac == NULL);\n        test_cond(astest.connect_status == REDIS_ERR);\n        config = defaultconfig;\n    }\n\n    /* Test a ping/pong after connection */\n    test(\"Async PING/PONG: \");\n    c = do_aconnect(config, ASTEST_PINGPONG);\n    while(astest.connected == 0)\n        redisPollTick(c, 0.1);\n    status = redisAsyncCommand(c, commandCallback, NULL, \"PING\");\n    assert(status == REDIS_OK);\n    while(astest.ac)\n        redisPollTick(c, 0.1);\n    test_cond(astest.pongs == 1);\n\n    /* Test a ping/pong after connection that didn't time out.\n     * see https://github.com/redis/hiredis/issues/945\n     */\n    if (config.type == CONN_TCP || config.type == CONN_SSL) {\n        test(\"Async PING/PONG after connect timeout: \");\n        config.connect_timeout.tv_usec = 10000; /* 10ms  */\n        c = do_aconnect(config, ASTEST_PINGPONG_TIMEOUT);\n        while(astest.connected == 0)\n            redisPollTick(c, 0.1);\n        /* sleep 0.1 s, allowing old timeout to arrive */\n        millisleep(10);\n        status = redisAsyncCommand(c, commandCallback, NULL, \"PING\");\n        assert(status == REDIS_OK);\n        while(astest.ac)\n            redisPollTick(c, 0.1);\n        test_cond(astest.pongs == 2);\n        config = defaultconfig;\n    }\n\n    /* Test disconnect from an on_connect callback\n     * see https://github.com/redis/hiredis/issues/931\n     */\n    test(\"Disconnect from onConnected callback (Issue #931): \");\n    c = do_aconnect(config, ASTEST_ISSUE_931);\n    while(astest.disconnects == 0)\n        redisPollTick(c, 0.1);\n    assert(astest.connected == 0);\n    assert(astest.connects == 1);\n    test_cond(astest.disconnects == 1);\n\n    /* Test ping/pong from an on_connect callback\n     * see https://github.com/redis/hiredis/issues/931\n     */\n    test(\"Ping/Pong from onConnected callback (Issue #931): \");\n    c = do_aconnect(config, ASTEST_ISSUE_931_PING);\n    /* connect callback issues ping, response callback destroys context */\n    while(astest.ac)\n        redisPollTick(c, 0.1);\n    assert(astest.connected == 0);\n    assert(astest.connects == 1);\n    assert(astest.disconnects == 1);\n    test_cond(astest.pongs == 1);\n}\n/* End of Async polling_adapter driven tests */\n\nint main(int argc, char **argv) {\n    struct config cfg = {\n        .tcp = {\n            .host = \"127.0.0.1\",\n            .port = 6379\n        },\n        .unix_sock = {\n            .path = \"/tmp/redis.sock\"\n        }\n    };\n    int throughput = 1;\n    int test_inherit_fd = 1;\n    int skips_as_fails = 0;\n    int test_unix_socket;\n\n    /* Parse command line options. */\n    argv++; argc--;\n    while (argc) {\n        if (argc >= 2 && !strcmp(argv[0],\"-h\")) {\n            argv++; argc--;\n            cfg.tcp.host = argv[0];\n        } else if (argc >= 2 && !strcmp(argv[0],\"-p\")) {\n            argv++; argc--;\n            cfg.tcp.port = atoi(argv[0]);\n        } else if (argc >= 2 && !strcmp(argv[0],\"-s\")) {\n            argv++; argc--;\n            cfg.unix_sock.path = argv[0];\n        } else if (argc >= 1 && !strcmp(argv[0],\"--skip-throughput\")) {\n            throughput = 0;\n        } else if (argc >= 1 && !strcmp(argv[0],\"--skip-inherit-fd\")) {\n            test_inherit_fd = 0;\n        } else if (argc >= 1 && !strcmp(argv[0],\"--skips-as-fails\")) {\n            skips_as_fails = 1;\n#ifdef HIREDIS_TEST_SSL\n        } else if (argc >= 2 && !strcmp(argv[0],\"--ssl-port\")) {\n            argv++; argc--;\n            cfg.ssl.port = atoi(argv[0]);\n        } else if (argc >= 2 && !strcmp(argv[0],\"--ssl-host\")) {\n            argv++; argc--;\n            cfg.ssl.host = argv[0];\n        } else if (argc >= 2 && !strcmp(argv[0],\"--ssl-ca-cert\")) {\n            argv++; argc--;\n            cfg.ssl.ca_cert  = argv[0];\n        } else if (argc >= 2 && !strcmp(argv[0],\"--ssl-cert\")) {\n            argv++; argc--;\n            cfg.ssl.cert = argv[0];\n        } else if (argc >= 2 && !strcmp(argv[0],\"--ssl-key\")) {\n            argv++; argc--;\n            cfg.ssl.key = argv[0];\n#endif\n        } else {\n            fprintf(stderr, \"Invalid argument: %s\\n\", argv[0]);\n            exit(1);\n        }\n        argv++; argc--;\n    }\n\n#ifndef _WIN32\n    /* Ignore broken pipe signal (for I/O error tests). */\n    signal(SIGPIPE, SIG_IGN);\n\n    test_unix_socket = access(cfg.unix_sock.path, F_OK) == 0;\n\n#else\n    /* Unix sockets don't exist in Windows */\n    test_unix_socket = 0;\n#endif\n\n    test_allocator_injection();\n\n    test_format_commands();\n    test_reply_reader();\n    test_blocking_connection_errors();\n    test_free_null();\n\n    printf(\"\\nTesting against TCP connection (%s:%d):\\n\", cfg.tcp.host, cfg.tcp.port);\n    cfg.type = CONN_TCP;\n    test_blocking_connection(cfg);\n    test_blocking_connection_timeouts(cfg);\n    test_blocking_io_errors(cfg);\n    test_invalid_timeout_errors(cfg);\n    test_append_formatted_commands(cfg);\n    test_tcp_options(cfg);\n    if (throughput) test_throughput(cfg);\n\n    printf(\"\\nTesting against Unix socket connection (%s): \", cfg.unix_sock.path);\n    if (test_unix_socket) {\n        printf(\"\\n\");\n        cfg.type = CONN_UNIX;\n        test_blocking_connection(cfg);\n        test_blocking_connection_timeouts(cfg);\n        test_blocking_io_errors(cfg);\n        test_invalid_timeout_errors(cfg);\n        test_unix_keepalive(cfg);\n        if (throughput) test_throughput(cfg);\n    } else {\n        test_skipped();\n    }\n\n#ifdef HIREDIS_TEST_SSL\n    if (cfg.ssl.port && cfg.ssl.host) {\n\n        redisInitOpenSSL();\n        _ssl_ctx = redisCreateSSLContext(cfg.ssl.ca_cert, NULL, cfg.ssl.cert, cfg.ssl.key, NULL, NULL);\n        assert(_ssl_ctx != NULL);\n\n        printf(\"\\nTesting against SSL connection (%s:%d):\\n\", cfg.ssl.host, cfg.ssl.port);\n        cfg.type = CONN_SSL;\n\n        test_blocking_connection(cfg);\n        test_blocking_connection_timeouts(cfg);\n        test_blocking_io_errors(cfg);\n        test_invalid_timeout_errors(cfg);\n        test_append_formatted_commands(cfg);\n        if (throughput) test_throughput(cfg);\n\n        redisFreeSSLContext(_ssl_ctx);\n        _ssl_ctx = NULL;\n    }\n#endif\n\n#ifdef HIREDIS_TEST_ASYNC\n    cfg.type = CONN_TCP;\n    printf(\"\\nTesting asynchronous API against TCP connection (%s:%d):\\n\", cfg.tcp.host, cfg.tcp.port);\n    cfg.type = CONN_TCP;\n\n    int major;\n    redisContext *c = do_connect(cfg);\n    get_redis_version(c, &major, NULL);\n    disconnect(c, 0);\n\n    test_pubsub_handling(cfg);\n    test_pubsub_multiple_channels(cfg);\n    test_monitor(cfg);\n    if (major >= 6) {\n        test_pubsub_handling_resp3(cfg);\n        test_command_timeout_during_pubsub(cfg);\n    }\n#endif /* HIREDIS_TEST_ASYNC */\n\n    cfg.type = CONN_TCP;\n    printf(\"\\nTesting asynchronous API using polling_adapter TCP (%s:%d):\\n\", cfg.tcp.host, cfg.tcp.port);\n    test_async_polling(cfg);\n    if (test_unix_socket) {\n        cfg.type = CONN_UNIX;\n        printf(\"\\nTesting asynchronous API using polling_adapter UNIX (%s):\\n\", cfg.unix_sock.path);\n        test_async_polling(cfg);\n    }\n\n    if (test_inherit_fd) {\n        printf(\"\\nTesting against inherited fd (%s): \", cfg.unix_sock.path);\n        if (test_unix_socket) {\n            printf(\"\\n\");\n            cfg.type = CONN_FD;\n            test_blocking_connection(cfg);\n        } else {\n            test_skipped();\n        }\n    }\n\n    if (fails || (skips_as_fails && skips)) {\n        printf(\"*** %d TESTS FAILED ***\\n\", fails);\n        if (skips) {\n            printf(\"*** %d TESTS SKIPPED ***\\n\", skips);\n        }\n        return 1;\n    }\n\n    printf(\"ALL TESTS PASSED (%d skipped)\\n\", skips);\n    return 0;\n}\n"
  },
  {
    "path": "test.sh",
    "content": "#!/bin/sh -ue\n\nREDIS_SERVER=${REDIS_SERVER:-redis-server}\nREDIS_PORT=${REDIS_PORT:-56379}\nREDIS_SSL_PORT=${REDIS_SSL_PORT:-56443}\nTEST_SSL=${TEST_SSL:-0}\nSKIPS_AS_FAILS=${SKIPS_AS_FAILS:-0}\nENABLE_DEBUG_CMD=\nSSL_TEST_ARGS=\nSKIPS_ARG=${SKIPS_ARG:-}\nREDIS_DOCKER=${REDIS_DOCKER:-}\n\n# We need to enable the DEBUG command for redis-server >= 7.0.0\nif [ -n \"${REDIS_DOCKER}\" ]; then\n    REDIS_VERSION=\"$(docker run --rm ${REDIS_DOCKER} ${REDIS_SERVER} --version)\"\nelse\n    REDIS_VERSION=\"$(${REDIS_SERVER} --version)\"\nfi\n\n# Enable debug command for redis >= 7.0.0\nREDIS_MAJOR_VERSION=\"$(echo ${REDIS_VERSION} | awk -F'[^0-9]+' '{ print $2 }')\"\nif [ \"$REDIS_MAJOR_VERSION\" -gt \"6\" ]; then\n    ENABLE_DEBUG_CMD=\"enable-debug-command local\"\nfi\n\ntmpdir=$(mktemp -d)\nPID_FILE=${tmpdir}/hiredis-test-redis.pid\nSOCK_FILE=${tmpdir}/hiredis-test-redis.sock\n\nif [ \"$TEST_SSL\" = \"1\" ]; then\n    SSL_CA_CERT=${tmpdir}/ca.crt\n    SSL_CA_KEY=${tmpdir}/ca.key\n    SSL_CERT=${tmpdir}/redis.crt\n    SSL_KEY=${tmpdir}/redis.key\n\n    openssl genrsa -out ${tmpdir}/ca.key 4096\n    openssl req \\\n        -x509 -new -nodes -sha256 \\\n        -key ${SSL_CA_KEY} \\\n        -days 3650 \\\n        -subj '/CN=Hiredis Test CA' \\\n        -out ${SSL_CA_CERT}\n    openssl genrsa -out ${SSL_KEY} 2048\n    openssl req \\\n        -new -sha256 \\\n        -key ${SSL_KEY} \\\n        -subj '/CN=Hiredis Test Cert' | \\\n        openssl x509 \\\n            -req -sha256 \\\n            -CA ${SSL_CA_CERT} \\\n            -CAkey ${SSL_CA_KEY} \\\n            -CAserial ${tmpdir}/ca.txt \\\n            -CAcreateserial \\\n            -days 365 \\\n            -out ${SSL_CERT}\n\n    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}\"\nfi\n\ncleanup() {\n  if [ -n \"${REDIS_DOCKER}\" ] ; then\n    docker kill redis-test-server\n  else\n    set +e\n    kill $(cat ${PID_FILE})\n  fi\n\n  # Make failure to remove the temp dir non-fatal\n  rm -rf -- \"${tmpdir}\" 2>/dev/null || {\n    echo \"warning: failed to remove temp dir: ${tmpdir}\" >&2\n  }\n}\ntrap cleanup INT TERM EXIT\n\n# base config\ncat > ${tmpdir}/redis.conf <<EOF\npidfile ${PID_FILE}\nport ${REDIS_PORT}\nunixsocket ${SOCK_FILE}\nunixsocketperm 777\nEOF\n\n# if not running in docker add these:\nif [ ! -n \"${REDIS_DOCKER}\" ]; then\ncat >> ${tmpdir}/redis.conf <<EOF\ndaemonize yes\n${ENABLE_DEBUG_CMD}\nbind 127.0.0.1\nEOF\nfi\n\n# if doing ssl, add these\nif [ \"$TEST_SSL\" = \"1\" ]; then\n    cat >> ${tmpdir}/redis.conf <<EOF\ntls-port ${REDIS_SSL_PORT}\ntls-ca-cert-file ${SSL_CA_CERT}\ntls-cert-file ${SSL_CERT}\ntls-key-file ${SSL_KEY}\nEOF\nfi\n\necho ${tmpdir}\ncat ${tmpdir}/redis.conf\nif [ -n \"${REDIS_DOCKER}\" ] ; then\n    chmod a+wx ${tmpdir}\n    chmod a+r ${tmpdir}/*\n    docker run -d --rm --name redis-test-server \\\n        -p ${REDIS_PORT}:${REDIS_PORT} \\\n        -p ${REDIS_SSL_PORT}:${REDIS_SSL_PORT} \\\n        -v ${tmpdir}:${tmpdir} \\\n        ${REDIS_DOCKER} \\\n        ${REDIS_SERVER} ${tmpdir}/redis.conf\nelse\n    ${REDIS_SERVER} ${tmpdir}/redis.conf\nfi\n# Wait until we detect the unix socket\necho waiting for server\nwhile [ ! -S \"${SOCK_FILE}\" ]; do sleep 1; done\n\n# Treat skips as failures if directed\n[ \"$SKIPS_AS_FAILS\" = 1 ] && SKIPS_ARG=\"${SKIPS_ARG} --skips-as-fails\"\n\n${TEST_PREFIX:-} ./hiredis-test -h 127.0.0.1 -p ${REDIS_PORT} -s ${SOCK_FILE} ${SSL_TEST_ARGS} ${SKIPS_ARG}\n"
  },
  {
    "path": "win32.h",
    "content": "#ifndef _WIN32_HELPER_INCLUDE\n#define _WIN32_HELPER_INCLUDE\n#ifdef _MSC_VER\n\n#include <winsock2.h> /* for struct timeval */\n\n#ifndef inline\n#define inline __inline\n#endif\n\n#ifndef strcasecmp\n#define strcasecmp stricmp\n#endif\n\n#ifndef strncasecmp\n#define strncasecmp strnicmp\n#endif\n\n#ifndef va_copy\n#define va_copy(d,s) ((d) = (s))\n#endif\n\n#ifndef snprintf\n#define snprintf c99_snprintf\n\n__inline int c99_vsnprintf(char* str, size_t size, const char* format, va_list ap)\n{\n    int count = -1;\n\n    if (size != 0)\n        count = _vsnprintf_s(str, size, _TRUNCATE, format, ap);\n    if (count == -1)\n        count = _vscprintf(format, ap);\n\n    return count;\n}\n\n__inline int c99_snprintf(char* str, size_t size, const char* format, ...)\n{\n    int count;\n    va_list ap;\n\n    va_start(ap, format);\n    count = c99_vsnprintf(str, size, format, ap);\n    va_end(ap);\n\n    return count;\n}\n#endif\n#endif /* _MSC_VER */\n\n#ifdef _WIN32\n#define strerror_r(errno,buf,len) strerror_s(buf,len,errno)\n#endif /* _WIN32 */\n\n#endif /* _WIN32_HELPER_INCLUDE */\n"
  }
]