diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 000000000..362bc77b7 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,205 @@ +name: Build and test +on: [push, pull_request] + +jobs: + ubuntu: + name: Ubuntu + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v2 + with: + repository: ${{ env.GITHUB_REPOSITORY }} + ref: ${{ env.GITHUB_HEAD_REF }} + + - name: Install dependencies + run: | + sudo add-apt-repository -y ppa:chris-lea/redis-server + sudo apt-get update + sudo apt-get install -y redis-server valgrind libevent-dev + + - name: Build using cmake + env: + EXTRA_CMAKE_OPTS: -DENABLE_EXAMPLES:BOOL=ON -DENABLE_SSL:BOOL=ON -DENABLE_SSL_TESTS:BOOL=ON -DENABLE_ASYNC_TESTS:BOOL=ON + CFLAGS: -Werror + CXXFLAGS: -Werror + run: mkdir build-ubuntu && cd build-ubuntu && cmake .. + + - name: Build using makefile + run: USE_SSL=1 TEST_ASYNC=1 make + + - name: Run tests + env: + SKIPS_AS_FAILS: 1 + TEST_SSL: 1 + run: $GITHUB_WORKSPACE/test.sh + + # - name: Run tests under valgrind + # env: + # SKIPS_AS_FAILS: 1 + # TEST_PREFIX: valgrind --error-exitcode=99 --track-origins=yes --leak-check=full + # run: $GITHUB_WORKSPACE/test.sh + + centos7: + name: CentOS 7 + runs-on: ubuntu-latest + container: centos:7 + steps: + - name: Checkout code + uses: actions/checkout@v2 + with: + repository: ${{ env.GITHUB_REPOSITORY }} + ref: ${{ env.GITHUB_HEAD_REF }} + + - name: Install dependencies + run: | + yum -y install http://rpms.remirepo.net/enterprise/remi-release-7.rpm + yum -y --enablerepo=remi install redis + yum -y install gcc gcc-c++ make openssl openssl-devel cmake3 valgrind libevent-devel + + - name: Build using cmake + env: + EXTRA_CMAKE_OPTS: -DENABLE_EXAMPLES:BOOL=ON -DENABLE_SSL:BOOL=ON -DENABLE_SSL_TESTS:BOOL=ON -DENABLE_ASYNC_TESTS:BOOL=ON + CFLAGS: -Werror + CXXFLAGS: -Werror + run: mkdir build-centos7 && cd build-centos7 && cmake3 .. + + - name: Build using Makefile + run: USE_SSL=1 TEST_ASYNC=1 make + + - name: Run tests + env: + SKIPS_AS_FAILS: 1 + TEST_SSL: 1 + run: $GITHUB_WORKSPACE/test.sh + + - name: Run tests under valgrind + env: + SKIPS_AS_FAILS: 1 + TEST_SSL: 1 + TEST_PREFIX: valgrind --error-exitcode=99 --track-origins=yes --leak-check=full + run: $GITHUB_WORKSPACE/test.sh + + centos8: + name: RockyLinux 8 + runs-on: ubuntu-latest + container: rockylinux:8 + steps: + - name: Checkout code + uses: actions/checkout@v2 + with: + repository: ${{ env.GITHUB_REPOSITORY }} + ref: ${{ env.GITHUB_HEAD_REF }} + + - name: Install dependencies + run: | + dnf -y install https://rpms.remirepo.net/enterprise/remi-release-8.rpm + dnf -y module install redis:remi-6.0 + dnf -y group install "Development Tools" + dnf -y install openssl-devel cmake valgrind libevent-devel + + - name: Build using cmake + env: + EXTRA_CMAKE_OPTS: -DENABLE_EXAMPLES:BOOL=ON -DENABLE_SSL:BOOL=ON -DENABLE_SSL_TESTS:BOOL=ON -DENABLE_ASYNC_TESTS:BOOL=ON + CFLAGS: -Werror + CXXFLAGS: -Werror + run: mkdir build-centos8 && cd build-centos8 && cmake .. + + - name: Build using Makefile + run: USE_SSL=1 TEST_ASYNC=1 make + + - name: Run tests + env: + SKIPS_AS_FAILS: 1 + TEST_SSL: 1 + run: $GITHUB_WORKSPACE/test.sh + + - name: Run tests under valgrind + env: + SKIPS_AS_FAILS: 1 + TEST_SSL: 1 + TEST_PREFIX: valgrind --error-exitcode=99 --track-origins=yes --leak-check=full + run: $GITHUB_WORKSPACE/test.sh + + freebsd: + runs-on: macos-10.15 + name: FreeBSD + steps: + - name: Checkout code + uses: actions/checkout@v2 + with: + repository: ${{ env.GITHUB_REPOSITORY }} + ref: ${{ env.GITHUB_HEAD_REF }} + + - name: Build in FreeBSD + uses: vmactions/freebsd-vm@v0.1.5 + with: + prepare: pkg install -y gmake cmake + run: | + mkdir build && cd build && cmake .. && make && cd .. + gmake + + macos: + name: macOS + runs-on: macos-latest + steps: + - name: Checkout code + uses: actions/checkout@v2 + with: + repository: ${{ env.GITHUB_REPOSITORY }} + ref: ${{ env.GITHUB_HEAD_REF }} + + - name: Install dependencies + run: | + brew install openssl redis + + - name: Build hiredis + run: USE_SSL=1 make + + - name: Run tests + env: + TEST_SSL: 1 + run: $GITHUB_WORKSPACE/test.sh + + windows: + name: Windows + runs-on: windows-latest + steps: + - name: Checkout code + uses: actions/checkout@v2 + with: + repository: ${{ env.GITHUB_REPOSITORY }} + ref: ${{ env.GITHUB_HEAD_REF }} + + - name: Install dependencies + run: | + choco install -y ninja memurai-developer + + - uses: ilammy/msvc-dev-cmd@v1 + - name: Build hiredis + run: | + mkdir build && cd build + cmake .. -G Ninja -DCMAKE_BUILD_TYPE=Release -DENABLE_EXAMPLES=ON + ninja -v + + - name: Run tests + run: | + ./build/hiredis-test.exe + + - name: Setup cygwin + uses: egor-tensin/setup-cygwin@v3 + with: + platform: x64 + packages: make git gcc-core + + - name: Build in cygwin + env: + HIREDIS_PATH: ${{ github.workspace }} + run: | + build_hiredis() { + cd $(cygpath -u $HIREDIS_PATH) + git clean -xfd + make + } + build_hiredis + shell: C:\tools\cygwin\bin\bash.exe --login --norc -eo pipefail -o igncr '{0}' diff --git a/.travis.yml b/.travis.yml index f9a9460ff..1e9b5569f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,11 +17,11 @@ branches: - /^release\/.*$/ install: - - if [ "$BITS" == "64" ]; then + - if [ "$TRAVIS_COMPILER" != "mingw" ]; then wget https://github.com/redis/redis/archive/6.0.6.tar.gz; tar -xzvf 6.0.6.tar.gz; pushd redis-6.0.6 && BUILD_TLS=yes make && export PATH=$PWD/src:$PATH && popd; - fi + fi; before_script: - if [ "$TRAVIS_OS_NAME" == "osx" ]; then @@ -33,8 +33,6 @@ before_script: addons: apt: - sources: - - sourceline: 'ppa:chris-lea/redis-server' packages: - libc6-dbg - libc6-dev @@ -46,17 +44,13 @@ addons: - libssl-dev - libssl-dev:i386 - valgrind - - redis env: - BITS="32" - BITS="64" script: - - EXTRA_CMAKE_OPTS="-DENABLE_EXAMPLES:BOOL=ON -DENABLE_SSL:BOOL=ON"; - if [ "$BITS" == "64" ]; then - EXTRA_CMAKE_OPTS="$EXTRA_CMAKE_OPTS -DENABLE_SSL_TESTS:BOOL=ON"; - fi; + - EXTRA_CMAKE_OPTS="-DENABLE_EXAMPLES:BOOL=ON -DENABLE_SSL:BOOL=ON -DENABLE_SSL_TESTS:BOOL=ON"; if [ "$TRAVIS_OS_NAME" == "osx" ]; then if [ "$BITS" == "32" ]; then CFLAGS="-m32 -Werror"; diff --git a/CHANGELOG.md b/CHANGELOG.md index 271f1fcf3..2a2bc314a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,22 @@ +## [1.0.2](https://github.com/redis/hiredis/tree/v1.0.2) - (2021-10-07) + +Announcing Hiredis v1.0.2, which fixes CVE-2021-32765 but returns the SONAME to the correct value of `1.0.0`. + +- [Revert SONAME bump](https://github.com/redis/hiredis/commit/d4e6f109a064690cde64765c654e679fea1d3548) + ([Michael Grunder](https://github.com/michael-grunder)) + +## [1.0.1](https://github.com/redis/hiredis/tree/v1.0.1) - (2021-10-04) + +This release erroneously bumped the SONAME, please use [1.0.2](https://github.com/redis/hiredis/tree/v1.0.2) + +Announcing Hiredis v1.0.1, a security release fixing CVE-2021-32765 + +- Fix for [CVE-2021-32765](https://github.com/redis/hiredis/security/advisories/GHSA-hfm9-39pp-55p2) + [commit](https://github.com/redis/hiredis/commit/76a7b10005c70babee357a7d0f2becf28ec7ed1e) + ([Yossi Gottlieb](https://github.com/yossigo)) + +_Thanks to [Yossi Gottlieb](https://github.com/yossigo) for the security fix and to [Microsoft Security Vulnerability Research](https://www.microsoft.com/en-us/msrc/msvr) for finding the bug._ :sparkling_heart: + ## [1.0.0](https://github.com/redis/hiredis/tree/v1.0.0) - (2020-08-03) Announcing Hiredis v1.0.0, which adds support for RESP3, SSL connections, allocator injection, and better Windows support! :tada: diff --git a/CMakeLists.txt b/CMakeLists.txt index f86c9b70b..fe6720b28 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,10 +1,9 @@ CMAKE_MINIMUM_REQUIRED(VERSION 3.4.0) -INCLUDE(GNUInstallDirs) -PROJECT(hiredis) OPTION(ENABLE_SSL "Build hiredis_ssl for SSL support" OFF) OPTION(DISABLE_TESTS "If tests should be compiled or not" OFF) -OPTION(ENABLE_SSL_TESTS, "Should we test SSL connections" OFF) +OPTION(ENABLE_SSL_TESTS "Should we test SSL connections" OFF) +OPTION(ENABLE_ASYNC_TESTS "Should we run all asynchronous API tests" OFF) MACRO(getVersionBit name) SET(VERSION_REGEX "^#define ${name} (.+)$") @@ -20,7 +19,13 @@ getVersionBit(HIREDIS_SONAME) SET(VERSION "${HIREDIS_MAJOR}.${HIREDIS_MINOR}.${HIREDIS_PATCH}") MESSAGE("Detected version: ${VERSION}") -PROJECT(hiredis VERSION "${VERSION}") +PROJECT(hiredis LANGUAGES "C" VERSION "${VERSION}") +INCLUDE(GNUInstallDirs) + +# Hiredis requires C99 +SET(CMAKE_C_STANDARD 99) +SET(CMAKE_POSITION_INDEPENDENT_CODE ON) +SET(CMAKE_DEBUG_POSTFIX d) SET(ENABLE_EXAMPLES OFF CACHE BOOL "Enable building hiredis examples") @@ -41,30 +46,84 @@ IF(WIN32) ENDIF() ADD_LIBRARY(hiredis SHARED ${hiredis_sources}) +ADD_LIBRARY(hiredis_static STATIC ${hiredis_sources}) +ADD_LIBRARY(hiredis::hiredis ALIAS hiredis) +ADD_LIBRARY(hiredis::hiredis_static ALIAS hiredis_static) SET_TARGET_PROPERTIES(hiredis PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS TRUE VERSION "${HIREDIS_SONAME}") +SET_TARGET_PROPERTIES(hiredis_static + PROPERTIES COMPILE_PDB_NAME hiredis_static) +SET_TARGET_PROPERTIES(hiredis_static + PROPERTIES COMPILE_PDB_NAME_DEBUG hiredis_static${CMAKE_DEBUG_POSTFIX}) IF(WIN32 OR MINGW) - TARGET_LINK_LIBRARIES(hiredis PRIVATE ws2_32) + TARGET_LINK_LIBRARIES(hiredis PUBLIC ws2_32 crypt32) + TARGET_LINK_LIBRARIES(hiredis_static PUBLIC ws2_32 crypt32) +ELSEIF(CMAKE_SYSTEM_NAME MATCHES "FreeBSD") + TARGET_LINK_LIBRARIES(hiredis PUBLIC m) + TARGET_LINK_LIBRARIES(hiredis_static PUBLIC m) +ELSEIF(CMAKE_SYSTEM_NAME MATCHES "SunOS") + TARGET_LINK_LIBRARIES(hiredis PUBLIC socket) + TARGET_LINK_LIBRARIES(hiredis_static PUBLIC socket) ENDIF() -TARGET_INCLUDE_DIRECTORIES(hiredis PUBLIC $ $) +TARGET_INCLUDE_DIRECTORIES(hiredis PUBLIC $ $) +TARGET_INCLUDE_DIRECTORIES(hiredis_static PUBLIC $ $) CONFIGURE_FILE(hiredis.pc.in hiredis.pc @ONLY) -INSTALL(TARGETS hiredis +set(CPACK_PACKAGE_VENDOR "Redis") +set(CPACK_PACKAGE_DESCRIPTION "\ +Hiredis is a minimalistic C client library for the Redis database. + +It is minimalistic because it just adds minimal support for the protocol, \ +but at the same time it uses a high level printf-alike API in order to make \ +it much higher level than otherwise suggested by its minimal code base and the \ +lack of explicit bindings for every Redis command. + +Apart from supporting sending commands and receiving replies, it comes with a \ +reply parser that is decoupled from the I/O layer. It is a stream parser designed \ +for easy reusability, which can for instance be used in higher level language bindings \ +for efficient reply parsing. + +Hiredis only supports the binary-safe Redis protocol, so you can use it with any Redis \ +version >= 1.2.0. + +The library comes with multiple APIs. There is the synchronous API, the asynchronous API \ +and the reply parsing API.") +set(CPACK_PACKAGE_HOMEPAGE_URL "https://github.com/redis/hiredis") +set(CPACK_PACKAGE_CONTACT "michael dot grunder at gmail dot com") +set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON) +set(CPACK_RPM_PACKAGE_AUTOREQPROV ON) + +include(CPack) + +INSTALL(TARGETS hiredis hiredis_static EXPORT hiredis-targets RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) +if (MSVC) + INSTALL(FILES $ + DESTINATION ${CMAKE_INSTALL_BINDIR} + CONFIGURATIONS Debug RelWithDebInfo) + INSTALL(FILES $/$.pdb + DESTINATION ${CMAKE_INSTALL_LIBDIR} + CONFIGURATIONS Debug RelWithDebInfo) +endif() + +# For NuGet packages +INSTALL(FILES hiredis.targets + DESTINATION build/native) + INSTALL(FILES hiredis.h read.h sds.h async.h alloc.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/hiredis) - + INSTALL(DIRECTORY adapters DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/hiredis) - + INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/hiredis.pc DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) @@ -95,10 +154,12 @@ IF(ENABLE_SSL) ENDIF() ENDIF() FIND_PACKAGE(OpenSSL REQUIRED) - SET(hiredis_ssl_sources + SET(hiredis_ssl_sources ssl.c) ADD_LIBRARY(hiredis_ssl SHARED ${hiredis_ssl_sources}) + ADD_LIBRARY(hiredis_ssl_static STATIC + ${hiredis_ssl_sources}) IF (APPLE) SET_PROPERTY(TARGET hiredis_ssl PROPERTY LINK_FLAGS "-Wl,-undefined -Wl,dynamic_lookup") @@ -108,23 +169,39 @@ IF(ENABLE_SSL) PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS TRUE VERSION "${HIREDIS_SONAME}") + SET_TARGET_PROPERTIES(hiredis_ssl_static + PROPERTIES COMPILE_PDB_NAME hiredis_ssl_static) + SET_TARGET_PROPERTIES(hiredis_ssl_static + PROPERTIES COMPILE_PDB_NAME_DEBUG hiredis_ssl_static${CMAKE_DEBUG_POSTFIX}) TARGET_INCLUDE_DIRECTORIES(hiredis_ssl PRIVATE "${OPENSSL_INCLUDE_DIR}") + TARGET_INCLUDE_DIRECTORIES(hiredis_ssl_static PRIVATE "${OPENSSL_INCLUDE_DIR}") + TARGET_LINK_LIBRARIES(hiredis_ssl PRIVATE ${OPENSSL_LIBRARIES}) IF (WIN32 OR MINGW) TARGET_LINK_LIBRARIES(hiredis_ssl PRIVATE hiredis) + TARGET_LINK_LIBRARIES(hiredis_ssl_static PUBLIC hiredis_static) ENDIF() CONFIGURE_FILE(hiredis_ssl.pc.in hiredis_ssl.pc @ONLY) - INSTALL(TARGETS hiredis_ssl + INSTALL(TARGETS hiredis_ssl hiredis_ssl_static EXPORT hiredis_ssl-targets RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) + if (MSVC) + INSTALL(FILES $ + DESTINATION ${CMAKE_INSTALL_BINDIR} + CONFIGURATIONS Debug RelWithDebInfo) + INSTALL(FILES $/$.pdb + DESTINATION ${CMAKE_INSTALL_LIBDIR} + CONFIGURATIONS Debug RelWithDebInfo) + endif() + INSTALL(FILES hiredis_ssl.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/hiredis) - + INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/hiredis_ssl.pc DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) @@ -149,11 +226,14 @@ ENDIF() IF(NOT DISABLE_TESTS) ENABLE_TESTING() ADD_EXECUTABLE(hiredis-test test.c) + TARGET_LINK_LIBRARIES(hiredis-test hiredis) IF(ENABLE_SSL_TESTS) ADD_DEFINITIONS(-DHIREDIS_TEST_SSL=1) - TARGET_LINK_LIBRARIES(hiredis-test hiredis hiredis_ssl) - ELSE() - TARGET_LINK_LIBRARIES(hiredis-test hiredis) + TARGET_LINK_LIBRARIES(hiredis-test hiredis_ssl) + ENDIF() + IF(ENABLE_ASYNC_TESTS) + ADD_DEFINITIONS(-DHIREDIS_TEST_ASYNC=1) + TARGET_LINK_LIBRARIES(hiredis-test event) ENDIF() ADD_TEST(NAME hiredis-test COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/test.sh) diff --git a/Makefile b/Makefile index a8d37a2eb..a2ad84c6b 100644 --- a/Makefile +++ b/Makefile @@ -4,16 +4,10 @@ # This file is released under the BSD license, see the COPYING file OBJ=alloc.o net.o hiredis.o sds.o async.o read.o sockcompat.o -SSL_OBJ=ssl.o EXAMPLES=hiredis-example hiredis-example-libevent hiredis-example-libev hiredis-example-glib hiredis-example-push -ifeq ($(USE_SSL),1) -EXAMPLES+=hiredis-example-ssl hiredis-example-libevent-ssl -endif TESTS=hiredis-test LIBNAME=libhiredis PKGCONFNAME=hiredis.pc -SSL_LIBNAME=libhiredis_ssl -SSL_PKGCONFNAME=hiredis_ssl.pc HIREDIS_MAJOR=$(shell grep HIREDIS_MAJOR hiredis.h | awk '{print $$3}') HIREDIS_MINOR=$(shell grep HIREDIS_MINOR hiredis.h | awk '{print $$3}') @@ -60,28 +54,66 @@ DYLIB_MAKE_CMD=$(CC) -shared -Wl,-soname,$(DYLIB_MINOR_NAME) STLIBNAME=$(LIBNAME).$(STLIBSUFFIX) STLIB_MAKE_CMD=$(AR) rcs +#################### SSL variables start #################### +SSL_OBJ=ssl.o +SSL_LIBNAME=libhiredis_ssl +SSL_PKGCONFNAME=hiredis_ssl.pc +SSL_INSTALLNAME=install-ssl SSL_DYLIB_MINOR_NAME=$(SSL_LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_SONAME) SSL_DYLIB_MAJOR_NAME=$(SSL_LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_MAJOR) SSL_DYLIBNAME=$(SSL_LIBNAME).$(DYLIBSUFFIX) SSL_STLIBNAME=$(SSL_LIBNAME).$(STLIBSUFFIX) SSL_DYLIB_MAKE_CMD=$(CC) -shared -Wl,-soname,$(SSL_DYLIB_MINOR_NAME) +USE_SSL?=0 +ifeq ($(USE_SSL),1) + # This is required for test.c only + CFLAGS+=-DHIREDIS_TEST_SSL + EXAMPLES+=hiredis-example-ssl hiredis-example-libevent-ssl + SSL_STLIB=$(SSL_STLIBNAME) + SSL_DYLIB=$(SSL_DYLIBNAME) + SSL_PKGCONF=$(SSL_PKGCONFNAME) + SSL_INSTALL=$(SSL_INSTALLNAME) +else + SSL_STLIB= + SSL_DYLIB= + SSL_PKGCONF= + SSL_INSTALL= +endif +##################### SSL variables end ##################### + + # Platform-specific overrides uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not') -USE_SSL?=0 - # This is required for test.c only -ifeq ($(USE_SSL),1) - CFLAGS+=-DHIREDIS_TEST_SSL +ifeq ($(TEST_ASYNC),1) + export CFLAGS+=-DHIREDIS_TEST_ASYNC endif -ifeq ($(uname_S),Linux) - SSL_LDFLAGS=-lssl -lcrypto +ifeq ($(USE_SSL),1) + ifeq ($(uname_S),Linux) + ifdef OPENSSL_PREFIX + CFLAGS+=-I$(OPENSSL_PREFIX)/include + SSL_LDFLAGS+=-L$(OPENSSL_PREFIX)/lib -lssl -lcrypto + else + SSL_LDFLAGS=-lssl -lcrypto + endif + else + OPENSSL_PREFIX?=/usr/local/opt/openssl + CFLAGS+=-I$(OPENSSL_PREFIX)/include + SSL_LDFLAGS+=-L$(OPENSSL_PREFIX)/lib -lssl -lcrypto + endif +endif + +ifeq ($(uname_S),FreeBSD) + LDFLAGS+=-lm + IS_GCC=$(shell sh -c '$(CC) --version 2>/dev/null |egrep -i -c "gcc"') + ifeq ($(IS_GCC),1) + REAL_CFLAGS+=-pedantic + endif else - OPENSSL_PREFIX?=/usr/local/opt/openssl - CFLAGS+=-I$(OPENSSL_PREFIX)/include - SSL_LDFLAGS+=-L$(OPENSSL_PREFIX)/lib -lssl -lcrypto + REAL_CFLAGS+=-pedantic endif ifeq ($(uname_S),SunOS) @@ -103,10 +135,13 @@ ifeq ($(uname_S),Darwin) DYLIB_PLUGIN=-Wl,-undefined -Wl,dynamic_lookup endif -all: $(DYLIBNAME) $(STLIBNAME) hiredis-test $(PKGCONFNAME) -ifeq ($(USE_SSL),1) -all: $(SSL_DYLIBNAME) $(SSL_STLIBNAME) $(SSL_PKGCONFNAME) -endif +all: dynamic static hiredis-test pkgconfig + +dynamic: $(DYLIBNAME) $(SSL_DYLIB) + +static: $(STLIBNAME) $(SSL_STLIB) + +pkgconfig: $(PKGCONFNAME) $(SSL_PKGCONF) # Deps (use make dep to generate this) alloc.o: alloc.c fmacros.h alloc.h @@ -117,7 +152,6 @@ net.o: net.c fmacros.h net.h hiredis.h read.h sds.h alloc.h sockcompat.h win32.h read.o: read.c fmacros.h alloc.h read.h sds.h win32.h sds.o: sds.c sds.h sdsalloc.h alloc.h sockcompat.o: sockcompat.c sockcompat.h -ssl.o: ssl.c hiredis.h read.h sds.h alloc.h async.h win32.h async_private.h test.o: test.c fmacros.h hiredis.h read.h sds.h alloc.h net.h sockcompat.h win32.h $(DYLIBNAME): $(OBJ) @@ -126,18 +160,15 @@ $(DYLIBNAME): $(OBJ) $(STLIBNAME): $(OBJ) $(STLIB_MAKE_CMD) $(STLIBNAME) $(OBJ) +#################### SSL building rules start #################### $(SSL_DYLIBNAME): $(SSL_OBJ) $(SSL_DYLIB_MAKE_CMD) $(DYLIB_PLUGIN) -o $(SSL_DYLIBNAME) $(SSL_OBJ) $(REAL_LDFLAGS) $(LDFLAGS) $(SSL_LDFLAGS) $(SSL_STLIBNAME): $(SSL_OBJ) $(STLIB_MAKE_CMD) $(SSL_STLIBNAME) $(SSL_OBJ) -dynamic: $(DYLIBNAME) -static: $(STLIBNAME) -ifeq ($(USE_SSL),1) -dynamic: $(SSL_DYLIBNAME) -static: $(SSL_STLIBNAME) -endif +$(SSL_OBJ): ssl.c hiredis.h read.h sds.h alloc.h async.h win32.h async_private.h +#################### SSL building rules end #################### # Binaries: hiredis-example-libevent: examples/example-libevent.c adapters/libevent.h $(STLIBNAME) @@ -161,7 +192,6 @@ hiredis-example-macosx: examples/example-macosx.c adapters/macosx.h $(STLIBNAME) hiredis-example-ssl: examples/example-ssl.c $(STLIBNAME) $(SSL_STLIBNAME) $(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< $(STLIBNAME) $(SSL_STLIBNAME) $(REAL_LDFLAGS) $(SSL_LDFLAGS) - ifndef AE_DIR hiredis-example-ae: @echo "Please specify AE_DIR (e.g. /src)" @@ -172,10 +202,11 @@ hiredis-example-ae: examples/example-ae.c adapters/ae.h $(STLIBNAME) endif ifndef LIBUV_DIR -hiredis-example-libuv: - @echo "Please specify LIBUV_DIR (e.g. ../libuv/)" - @false +# dynamic link libuv.so +hiredis-example-libuv: examples/example-libuv.c adapters/libuv.h $(STLIBNAME) + $(CC) -o examples/$@ $(REAL_CFLAGS) -I. -I$(LIBUV_DIR)/include $< -luv -lpthread -lrt $(STLIBNAME) $(REAL_LDFLAGS) else +# use user provided static lib hiredis-example-libuv: examples/example-libuv.c adapters/libuv.h $(STLIBNAME) $(CC) -o examples/$@ $(REAL_CFLAGS) -I. -I$(LIBUV_DIR)/include $< $(LIBUV_DIR)/.libs/libuv.a -lpthread -lrt $(STLIBNAME) $(REAL_LDFLAGS) endif @@ -201,10 +232,13 @@ hiredis-example-push: examples/example-push.c $(STLIBNAME) examples: $(EXAMPLES) -TEST_LIBS = $(STLIBNAME) +TEST_LIBS = $(STLIBNAME) $(SSL_STLIB) +TEST_LDFLAGS = $(SSL_LDFLAGS) ifeq ($(USE_SSL),1) - TEST_LIBS += $(SSL_STLIBNAME) - TEST_LDFLAGS = $(SSL_LDFLAGS) -lssl -lcrypto -lpthread + TEST_LDFLAGS += -pthread +endif +ifeq ($(TEST_ASYNC),1) + TEST_LDFLAGS += -levent endif hiredis-test: test.o $(TEST_LIBS) @@ -220,7 +254,7 @@ check: hiredis-test TEST_SSL=$(USE_SSL) ./test.sh .c.o: - $(CC) -std=c99 -pedantic -c $(REAL_CFLAGS) $< + $(CC) -std=c99 -c $(REAL_CFLAGS) $< clean: rm -rf $(DYLIBNAME) $(STLIBNAME) $(SSL_DYLIBNAME) $(SSL_STLIBNAME) $(TESTS) $(PKGCONFNAME) examples/hiredis-example* *.o *.gcda *.gcno *.gcov @@ -257,7 +291,7 @@ $(SSL_PKGCONFNAME): hiredis_ssl.h @echo Libs: -L\$${libdir} -lhiredis_ssl >> $@ @echo Libs.private: -lssl -lcrypto >> $@ -install: $(DYLIBNAME) $(STLIBNAME) $(PKGCONFNAME) +install: $(DYLIBNAME) $(STLIBNAME) $(PKGCONFNAME) $(SSL_INSTALL) mkdir -p $(INSTALL_INCLUDE_PATH) $(INSTALL_INCLUDE_PATH)/adapters $(INSTALL_LIBRARY_PATH) $(INSTALL) hiredis.h async.h read.h sds.h alloc.h $(INSTALL_INCLUDE_PATH) $(INSTALL) adapters/*.h $(INSTALL_INCLUDE_PATH)/adapters @@ -267,9 +301,6 @@ install: $(DYLIBNAME) $(STLIBNAME) $(PKGCONFNAME) mkdir -p $(INSTALL_PKGCONF_PATH) $(INSTALL) $(PKGCONFNAME) $(INSTALL_PKGCONF_PATH) -ifeq ($(USE_SSL),1) -install: install-ssl - install-ssl: $(SSL_DYLIBNAME) $(SSL_STLIBNAME) $(SSL_PKGCONFNAME) mkdir -p $(INSTALL_INCLUDE_PATH) $(INSTALL_LIBRARY_PATH) $(INSTALL) hiredis_ssl.h $(INSTALL_INCLUDE_PATH) @@ -278,7 +309,6 @@ install-ssl: $(SSL_DYLIBNAME) $(SSL_STLIBNAME) $(SSL_PKGCONFNAME) $(INSTALL) $(SSL_STLIBNAME) $(INSTALL_LIBRARY_PATH) mkdir -p $(INSTALL_PKGCONF_PATH) $(INSTALL) $(SSL_PKGCONFNAME) $(INSTALL_PKGCONF_PATH) -endif 32bit: @echo "" @@ -294,12 +324,12 @@ gprof: $(MAKE) CFLAGS="-pg" LDFLAGS="-pg" gcov: - $(MAKE) CFLAGS="-fprofile-arcs -ftest-coverage" LDFLAGS="-fprofile-arcs" + $(MAKE) CFLAGS+="-fprofile-arcs -ftest-coverage" LDFLAGS="-fprofile-arcs" coverage: gcov make check mkdir -p tmp/lcov - lcov -d . -c -o tmp/lcov/hiredis.info + lcov -d . -c --exclude '/usr*' -o tmp/lcov/hiredis.info genhtml --legend -o tmp/lcov/report tmp/lcov/hiredis.info noopt: diff --git a/README.md b/README.md index 3a22553ea..ed66220c7 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,11 @@ -[![Build Status](https://travis-ci.org/redis/hiredis.png)](https://travis-ci.org/redis/hiredis) + +[![Build Status](https://github.com/redis/hiredis/actions/workflows/build.yml/badge.svg)](https://github.com/redis/hiredis/actions/workflows/build.yml) **This Readme reflects the latest changed in the master branch. See [v1.0.0](https://github.com/redis/hiredis/tree/v1.0.0) for the Readme and documentation for the latest release ([API/ABI history](https://abi-laboratory.pro/?view=timeline&l=hiredis)).** # HIREDIS -Hiredis is a minimalistic C client library for the [Redis](http://redis.io/) database. +Hiredis is a minimalistic C client library for the [Redis](https://redis.io/) database. It is minimalistic because it just adds minimal support for the protocol, but at the same time it uses a high level printf-alike API in order to make it @@ -22,6 +23,12 @@ Redis version >= 1.2.0. The library comes with multiple APIs. There is the *synchronous API*, the *asynchronous API* and the *reply parsing API*. +## Upgrading to `1.0.2` + +NOTE: v1.0.1 erroneously bumped SONAME, which is why it is skipped here. + +Version 1.0.2 is simply 1.0.0 with a fix for [CVE-2021-32765](https://github.com/redis/hiredis/security/advisories/GHSA-hfm9-39pp-55p2). They are otherwise identical. + ## Upgrading to `1.0.0` Version 1.0.0 marks the first stable release of Hiredis. @@ -169,7 +176,7 @@ Hiredis also supports every new `RESP3` data type which are as follows. For mor * **`REDIS_REPLY_MAP`**: * An array with the added invariant that there will always be an even number of elements. - The MAP is functionally equivelant to `REDIS_REPLY_ARRAY` except for the previously mentioned invariant. + The MAP is functionally equivalent to `REDIS_REPLY_ARRAY` except for the previously mentioned invariant. * **`REDIS_REPLY_SET`**: * An array response where each entry is unique. @@ -189,7 +196,7 @@ Hiredis also supports every new `RESP3` data type which are as follows. For mor * **`REDIS_REPLY_VERB`**: * A verbatim string, intended to be presented to the user without modification. - The string payload is stored in the `str` memeber, and type data is stored in the `vtype` member (e.g. `txt` for raw text or `md` for markdown). + The string payload is stored in the `str` member, and type data is stored in the `vtype` member (e.g. `txt` for raw text or `md` for markdown). Replies should be freed using the `freeReplyObject()` function. Note that this function will take care of freeing sub-reply objects @@ -261,9 +268,9 @@ a single call to `read(2)`): redisReply *reply; redisAppendCommand(context,"SET foo bar"); redisAppendCommand(context,"GET foo"); -redisGetReply(context,(void *)&reply); // reply for SET +redisGetReply(context,(void**)&reply); // reply for SET freeReplyObject(reply); -redisGetReply(context,(void *)&reply); // reply for GET +redisGetReply(context,(void**)&reply); // reply for GET freeReplyObject(reply); ``` This API can also be used to implement a blocking subscriber: @@ -517,7 +524,7 @@ initialize OpenSSL and create a context. You can do that in two ways: /* An Hiredis SSL context. It holds SSL configuration and can be reused across * many contexts. */ -redisSSLContext *ssl; +redisSSLContext *ssl_context; /* An error variable to indicate what went wrong, if the context fails to * initialize. @@ -532,17 +539,23 @@ redisSSLContextError ssl_error; redisInitOpenSSL(); /* Create SSL context */ -ssl = redisCreateSSLContext( +ssl_context = redisCreateSSLContext( "cacertbundle.crt", /* File name of trusted CA/ca bundle file, optional */ "/path/to/certs", /* Path of trusted certificates, optional */ "client_cert.pem", /* File name of client certificate file, optional */ "client_key.pem", /* File name of client private key, optional */ "redis.mydomain.com", /* Server name to request (SNI), optional */ - &ssl_error - ) != REDIS_OK) { - printf("SSL error: %s\n", redisSSLContextGetError(ssl_error); - /* Abort... */ - } + &ssl_error); + +if(ssl_context == NULL || ssl_error != 0) { + /* Handle error and abort... */ + /* e.g. + printf("SSL error: %s\n", + (ssl_error != 0) ? + redisSSLContextGetError(ssl_error) : "Unknown error"); + // Abort + */ +} /* Create Redis context and establish connection */ c = redisConnect("localhost", 6443); @@ -551,7 +564,7 @@ if (c == NULL || c->err) { } /* Negotiate SSL/TLS */ -if (redisInitiateSSLWithContext(c, ssl) != REDIS_OK) { +if (redisInitiateSSLWithContext(c, ssl_context) != REDIS_OK) { /* Handle error, in c->err / c->errstr */ } ``` diff --git a/adapters/libev.h b/adapters/libev.h index e1e7bbd99..c59d3da77 100644 --- a/adapters/libev.h +++ b/adapters/libev.h @@ -46,7 +46,7 @@ typedef struct redisLibevEvents { static void redisLibevReadEvent(EV_P_ ev_io *watcher, int revents) { #if EV_MULTIPLICITY - ((void)loop); + ((void)EV_A); #endif ((void)revents); @@ -56,7 +56,7 @@ static void redisLibevReadEvent(EV_P_ ev_io *watcher, int revents) { static void redisLibevWriteEvent(EV_P_ ev_io *watcher, int revents) { #if EV_MULTIPLICITY - ((void)loop); + ((void)EV_A); #endif ((void)revents); @@ -66,8 +66,9 @@ static void redisLibevWriteEvent(EV_P_ ev_io *watcher, int revents) { static void redisLibevAddRead(void *privdata) { redisLibevEvents *e = (redisLibevEvents*)privdata; +#if EV_MULTIPLICITY struct ev_loop *loop = e->loop; - ((void)loop); +#endif if (!e->reading) { e->reading = 1; ev_io_start(EV_A_ &e->rev); @@ -76,8 +77,9 @@ static void redisLibevAddRead(void *privdata) { static void redisLibevDelRead(void *privdata) { redisLibevEvents *e = (redisLibevEvents*)privdata; +#if EV_MULTIPLICITY struct ev_loop *loop = e->loop; - ((void)loop); +#endif if (e->reading) { e->reading = 0; ev_io_stop(EV_A_ &e->rev); @@ -86,8 +88,9 @@ static void redisLibevDelRead(void *privdata) { static void redisLibevAddWrite(void *privdata) { redisLibevEvents *e = (redisLibevEvents*)privdata; +#if EV_MULTIPLICITY struct ev_loop *loop = e->loop; - ((void)loop); +#endif if (!e->writing) { e->writing = 1; ev_io_start(EV_A_ &e->wev); @@ -96,8 +99,9 @@ static void redisLibevAddWrite(void *privdata) { static void redisLibevDelWrite(void *privdata) { redisLibevEvents *e = (redisLibevEvents*)privdata; +#if EV_MULTIPLICITY struct ev_loop *loop = e->loop; - ((void)loop); +#endif if (e->writing) { e->writing = 0; ev_io_stop(EV_A_ &e->wev); @@ -106,8 +110,9 @@ static void redisLibevDelWrite(void *privdata) { static void redisLibevStopTimer(void *privdata) { redisLibevEvents *e = (redisLibevEvents*)privdata; +#if EV_MULTIPLICITY struct ev_loop *loop = e->loop; - ((void)loop); +#endif ev_timer_stop(EV_A_ &e->timer); } @@ -120,6 +125,9 @@ static void redisLibevCleanup(void *privdata) { } static void redisLibevTimeout(EV_P_ ev_timer *timer, int revents) { +#if EV_MULTIPLICITY + ((void)EV_A); +#endif ((void)revents); redisLibevEvents *e = (redisLibevEvents*)timer->data; redisAsyncHandleTimeout(e->context); @@ -127,8 +135,9 @@ static void redisLibevTimeout(EV_P_ ev_timer *timer, int revents) { static void redisLibevSetTimeout(void *privdata, struct timeval tv) { redisLibevEvents *e = (redisLibevEvents*)privdata; +#if EV_MULTIPLICITY struct ev_loop *loop = e->loop; - ((void)loop); +#endif if (!ev_is_active(&e->timer)) { ev_init(&e->timer, redisLibevTimeout); @@ -154,7 +163,7 @@ static int redisLibevAttach(EV_P_ redisAsyncContext *ac) { e->context = ac; #if EV_MULTIPLICITY - e->loop = loop; + e->loop = EV_A; #else e->loop = NULL; #endif diff --git a/adapters/libevent.h b/adapters/libevent.h index 9150979bc..73bb8ed75 100644 --- a/adapters/libevent.h +++ b/adapters/libevent.h @@ -50,7 +50,7 @@ static void redisLibeventDestroy(redisLibeventEvents *e) { hi_free(e); } -static void redisLibeventHandler(int fd, short event, void *arg) { +static void redisLibeventHandler(evutil_socket_t fd, short event, void *arg) { ((void)fd); redisLibeventEvents *e = (redisLibeventEvents*)arg; e->state |= REDIS_LIBEVENT_ENTERED; diff --git a/adapters/libuv.h b/adapters/libuv.h index c120b1b39..df0a84578 100644 --- a/adapters/libuv.h +++ b/adapters/libuv.h @@ -7,111 +7,157 @@ #include typedef struct redisLibuvEvents { - redisAsyncContext* context; - uv_poll_t handle; - int events; + redisAsyncContext* context; + uv_poll_t handle; + uv_timer_t timer; + int events; } redisLibuvEvents; static void redisLibuvPoll(uv_poll_t* handle, int status, int events) { - redisLibuvEvents* p = (redisLibuvEvents*)handle->data; - int ev = (status ? p->events : events); + redisLibuvEvents* p = (redisLibuvEvents*)handle->data; + int ev = (status ? p->events : events); - if (p->context != NULL && (ev & UV_READABLE)) { - redisAsyncHandleRead(p->context); - } - if (p->context != NULL && (ev & UV_WRITABLE)) { - redisAsyncHandleWrite(p->context); - } + if (p->context != NULL && (ev & UV_READABLE)) { + redisAsyncHandleRead(p->context); + } + if (p->context != NULL && (ev & UV_WRITABLE)) { + redisAsyncHandleWrite(p->context); + } } static void redisLibuvAddRead(void *privdata) { - redisLibuvEvents* p = (redisLibuvEvents*)privdata; + redisLibuvEvents* p = (redisLibuvEvents*)privdata; - p->events |= UV_READABLE; + p->events |= UV_READABLE; - uv_poll_start(&p->handle, p->events, redisLibuvPoll); + uv_poll_start(&p->handle, p->events, redisLibuvPoll); } static void redisLibuvDelRead(void *privdata) { - redisLibuvEvents* p = (redisLibuvEvents*)privdata; + redisLibuvEvents* p = (redisLibuvEvents*)privdata; - p->events &= ~UV_READABLE; + p->events &= ~UV_READABLE; - if (p->events) { - uv_poll_start(&p->handle, p->events, redisLibuvPoll); - } else { - uv_poll_stop(&p->handle); - } + if (p->events) { + uv_poll_start(&p->handle, p->events, redisLibuvPoll); + } else { + uv_poll_stop(&p->handle); + } } static void redisLibuvAddWrite(void *privdata) { - redisLibuvEvents* p = (redisLibuvEvents*)privdata; + redisLibuvEvents* p = (redisLibuvEvents*)privdata; - p->events |= UV_WRITABLE; + p->events |= UV_WRITABLE; - uv_poll_start(&p->handle, p->events, redisLibuvPoll); + uv_poll_start(&p->handle, p->events, redisLibuvPoll); } static void redisLibuvDelWrite(void *privdata) { - redisLibuvEvents* p = (redisLibuvEvents*)privdata; + redisLibuvEvents* p = (redisLibuvEvents*)privdata; - p->events &= ~UV_WRITABLE; + p->events &= ~UV_WRITABLE; - if (p->events) { - uv_poll_start(&p->handle, p->events, redisLibuvPoll); - } else { - uv_poll_stop(&p->handle); - } + if (p->events) { + uv_poll_start(&p->handle, p->events, redisLibuvPoll); + } else { + uv_poll_stop(&p->handle); + } } - -static void on_close(uv_handle_t* handle) { - redisLibuvEvents* p = (redisLibuvEvents*)handle->data; - - hi_free(p); +static void on_timer_close(uv_handle_t *handle) { + redisLibuvEvents* p = (redisLibuvEvents*)handle->data; + p->timer.data = NULL; + if (!p->handle.data) { + // both timer and handle are closed + hi_free(p); + } + // else, wait for `on_handle_close` } +static void on_handle_close(uv_handle_t *handle) { + redisLibuvEvents* p = (redisLibuvEvents*)handle->data; + p->handle.data = NULL; + if (!p->timer.data) { + // timer never started, or timer already destroyed + hi_free(p); + } + // else, wait for `on_timer_close` +} + +// libuv removed `status` parameter since v0.11.23 +// see: https://github.com/libuv/libuv/blob/v0.11.23/include/uv.h +#if (UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR < 11) || \ + (UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR == 11 && UV_VERSION_PATCH < 23) +static void redisLibuvTimeout(uv_timer_t *timer, int status) { + (void)status; // unused +#else +static void redisLibuvTimeout(uv_timer_t *timer) { +#endif + redisLibuvEvents *e = (redisLibuvEvents*)timer->data; + redisAsyncHandleTimeout(e->context); +} + +static void redisLibuvSetTimeout(void *privdata, struct timeval tv) { + redisLibuvEvents* p = (redisLibuvEvents*)privdata; + + uint64_t millsec = tv.tv_sec * 1000 + tv.tv_usec / 1000.0; + if (!p->timer.data) { + // timer is uninitialized + if (uv_timer_init(p->handle.loop, &p->timer) != 0) { + return; + } + p->timer.data = p; + } + // updates the timeout if the timer has already started + // or start the timer + uv_timer_start(&p->timer, redisLibuvTimeout, millsec, 0); +} static void redisLibuvCleanup(void *privdata) { - redisLibuvEvents* p = (redisLibuvEvents*)privdata; + redisLibuvEvents* p = (redisLibuvEvents*)privdata; - p->context = NULL; // indicate that context might no longer exist - uv_close((uv_handle_t*)&p->handle, on_close); + p->context = NULL; // indicate that context might no longer exist + if (p->timer.data) { + uv_close((uv_handle_t*)&p->timer, on_timer_close); + } + uv_close((uv_handle_t*)&p->handle, on_handle_close); } static int redisLibuvAttach(redisAsyncContext* ac, uv_loop_t* loop) { - redisContext *c = &(ac->c); + redisContext *c = &(ac->c); - if (ac->ev.data != NULL) { - return REDIS_ERR; - } + if (ac->ev.data != NULL) { + return REDIS_ERR; + } - ac->ev.addRead = redisLibuvAddRead; - ac->ev.delRead = redisLibuvDelRead; - ac->ev.addWrite = redisLibuvAddWrite; - ac->ev.delWrite = redisLibuvDelWrite; - ac->ev.cleanup = redisLibuvCleanup; + ac->ev.addRead = redisLibuvAddRead; + ac->ev.delRead = redisLibuvDelRead; + ac->ev.addWrite = redisLibuvAddWrite; + ac->ev.delWrite = redisLibuvDelWrite; + ac->ev.cleanup = redisLibuvCleanup; + ac->ev.scheduleTimer = redisLibuvSetTimeout; - redisLibuvEvents* p = (redisLibuvEvents*)hi_malloc(sizeof(*p)); - if (p == NULL) - return REDIS_ERR; + redisLibuvEvents* p = (redisLibuvEvents*)hi_malloc(sizeof(*p)); + if (p == NULL) + return REDIS_ERR; - memset(p, 0, sizeof(*p)); + memset(p, 0, sizeof(*p)); - if (uv_poll_init_socket(loop, &p->handle, c->fd) != 0) { - return REDIS_ERR; - } + if (uv_poll_init_socket(loop, &p->handle, c->fd) != 0) { + return REDIS_ERR; + } - ac->ev.data = p; - p->handle.data = p; - p->context = ac; + ac->ev.data = p; + p->handle.data = p; + p->context = ac; - return REDIS_OK; + return REDIS_OK; } #endif diff --git a/alloc.c b/alloc.c index 7fb6b35e7..0902286c7 100644 --- a/alloc.c +++ b/alloc.c @@ -68,6 +68,10 @@ void *hi_malloc(size_t size) { } void *hi_calloc(size_t nmemb, size_t size) { + /* Overflow check as the user can specify any arbitrary allocator */ + if (SIZE_MAX / size < nmemb) + return NULL; + return hiredisAllocFns.callocFn(nmemb, size); } diff --git a/alloc.h b/alloc.h index 34a05f49f..771f9fee5 100644 --- a/alloc.h +++ b/alloc.h @@ -32,6 +32,7 @@ #define HIREDIS_ALLOC_H #include /* for size_t */ +#include #ifdef __cplusplus extern "C" { @@ -59,6 +60,10 @@ static inline void *hi_malloc(size_t size) { } static inline void *hi_calloc(size_t nmemb, size_t size) { + /* Overflow check as the user can specify any arbitrary allocator */ + if (SIZE_MAX / size < nmemb) + return NULL; + return hiredisAllocFns.callocFn(nmemb, size); } diff --git a/async.c b/async.c index 64ab601c9..65551142b 100644 --- a/async.c +++ b/async.c @@ -47,6 +47,11 @@ #include "async_private.h" +#ifdef NDEBUG +#undef assert +#define assert(e) (void)(e) +#endif + /* Forward declarations of hiredis.c functions */ int __redisAppendCommand(redisContext *c, const char *cmd, size_t len); void __redisSetError(redisContext *c, int type, const char *str); @@ -54,7 +59,7 @@ void __redisSetError(redisContext *c, int type, const char *str); /* Functions managing dictionary of callbacks for pub/sub. */ static unsigned int callbackHash(const void *key) { return dictGenHashFunction((const unsigned char *)key, - hi_sdslen((const hisds)key)); + sdslen((const sds)key)); } static void *callbackValDup(void *privdata, const void *src) { @@ -73,15 +78,15 @@ static int callbackKeyCompare(void *privdata, const void *key1, const void *key2 int l1, l2; ((void) privdata); - l1 = hi_sdslen((const hisds)key1); - l2 = hi_sdslen((const hisds)key2); + l1 = sdslen((const sds)key1); + l2 = sdslen((const sds)key2); if (l1 != l2) return 0; return memcmp(key1,key2,l1) == 0; } static void callbackKeyDestructor(void *privdata, void *key) { ((void) privdata); - hi_sdsfree((hisds)key); + sdsfree((sds)key); } static void callbackValDestructor(void *privdata, void *val) { @@ -139,8 +144,8 @@ static redisAsyncContext *redisAsyncInitialize(redisContext *c) { ac->replies.head = NULL; ac->replies.tail = NULL; - ac->sub.invalid.head = NULL; - ac->sub.invalid.tail = NULL; + ac->sub.replies.head = NULL; + ac->sub.replies.tail = NULL; ac->sub.channels = channels; ac->sub.patterns = patterns; @@ -301,36 +306,28 @@ static void __redisRunPushCallback(redisAsyncContext *ac, redisReply *reply) { static void __redisAsyncFree(redisAsyncContext *ac) { redisContext *c = &(ac->c); redisCallback cb; - dictIterator *it; + dictIterator it; dictEntry *de; /* Execute pending callbacks with NULL reply. */ while (__redisShiftCallback(&ac->replies,&cb) == REDIS_OK) __redisRunCallback(ac,&cb,NULL); - - /* Execute callbacks for invalid commands */ - while (__redisShiftCallback(&ac->sub.invalid,&cb) == REDIS_OK) + while (__redisShiftCallback(&ac->sub.replies,&cb) == REDIS_OK) __redisRunCallback(ac,&cb,NULL); /* Run subscription callbacks with NULL reply */ if (ac->sub.channels) { - it = dictGetIterator(ac->sub.channels); - if (it != NULL) { - while ((de = dictNext(it)) != NULL) - __redisRunCallback(ac,dictGetEntryVal(de),NULL); - dictReleaseIterator(it); - } + dictInitIterator(&it,ac->sub.channels); + while ((de = dictNext(&it)) != NULL) + __redisRunCallback(ac,dictGetEntryVal(de),NULL); dictRelease(ac->sub.channels); } if (ac->sub.patterns) { - it = dictGetIterator(ac->sub.patterns); - if (it != NULL) { - while ((de = dictNext(it)) != NULL) - __redisRunCallback(ac,dictGetEntryVal(de),NULL); - dictReleaseIterator(it); - } + dictInitIterator(&it,ac->sub.patterns); + while ((de = dictNext(&it)) != NULL) + __redisRunCallback(ac,dictGetEntryVal(de),NULL); dictRelease(ac->sub.patterns); } @@ -418,12 +415,13 @@ static int __redisGetSubscribeCallback(redisAsyncContext *ac, redisReply *reply, dictEntry *de; int pvariant; char *stype; - hisds sname; + sds sname; - /* Custom reply functions are not supported for pub/sub. This will fail - * very hard when they are used... */ - if (reply->type == REDIS_REPLY_ARRAY || reply->type == REDIS_REPLY_PUSH) { - assert(reply->elements >= 2); + /* Match reply with the expected format of a pushed message. + * The type and number of elements (3 to 4) are specified at: + * https://redis.io/topics/pubsub#format-of-pushed-messages */ + if ((reply->type == REDIS_REPLY_ARRAY && !(c->flags & REDIS_SUPPORTS_PUSH) && reply->elements >= 3) || + reply->type == REDIS_REPLY_PUSH) { assert(reply->element[0]->type == REDIS_REPLY_STRING); stype = reply->element[0]->str; pvariant = (tolower(stype[0]) == 'p') ? 1 : 0; @@ -435,7 +433,7 @@ static int __redisGetSubscribeCallback(redisAsyncContext *ac, redisReply *reply, /* Locate the right callback */ assert(reply->element[1]->type == REDIS_REPLY_STRING); - sname = hi_sdsnewlen(reply->element[1]->str,reply->element[1]->len); + sname = sdsnewlen(reply->element[1]->str,reply->element[1]->len); if (sname == NULL) goto oom; @@ -462,14 +460,21 @@ static int __redisGetSubscribeCallback(redisAsyncContext *ac, redisReply *reply, /* Unset subscribed flag only when no pipelined pending subscribe. */ if (reply->element[2]->integer == 0 && dictSize(ac->sub.channels) == 0 - && dictSize(ac->sub.patterns) == 0) + && dictSize(ac->sub.patterns) == 0) { c->flags &= ~REDIS_SUBSCRIBED; + + /* Move ongoing regular command callbacks. */ + redisCallback cb; + while (__redisShiftCallback(&ac->sub.replies,&cb) == REDIS_OK) { + __redisPushCallback(&ac->replies,&cb); + } + } } } - hi_sdsfree(sname); + sdsfree(sname); } else { - /* Shift callback for invalid commands. */ - __redisShiftCallback(&ac->sub.invalid,dstcb); + /* Shift callback for pending command in subscribed context. */ + __redisShiftCallback(&ac->sub.replies,dstcb); } return REDIS_OK; oom: @@ -497,13 +502,12 @@ static int redisIsSubscribeReply(redisReply *reply) { len = reply->element[0]->len - off; return !strncasecmp(str, "subscribe", len) || - !strncasecmp(str, "message", len); - + !strncasecmp(str, "message", len) || + !strncasecmp(str, "unsubscribe", len); } void redisProcessCallbacks(redisAsyncContext *ac) { redisContext *c = &(ac->c); - redisCallback cb = {NULL, NULL, 0, NULL}; void *reply = NULL; int status; @@ -511,22 +515,19 @@ void redisProcessCallbacks(redisAsyncContext *ac) { if (reply == NULL) { /* When the connection is being disconnected and there are * no more replies, this is the cue to really disconnect. */ - if (c->flags & REDIS_DISCONNECTING && hi_sdslen(c->obuf) == 0 + if (c->flags & REDIS_DISCONNECTING && sdslen(c->obuf) == 0 && ac->replies.head == NULL) { __redisAsyncDisconnect(ac); return; } - - /* If monitor mode, repush callback */ - if(c->flags & REDIS_MONITORING) { - __redisPushCallback(&ac->replies,&cb); - } - /* When the connection is not being disconnected, simply stop * trying to get replies and wait for the next loop tick. */ break; } + /* Keep track of push message support for subscribe handling */ + if (redisIsPushReply(reply)) c->flags |= REDIS_SUPPORTS_PUSH; + /* Send any non-subscribe related PUSH messages to our PUSH handler * while allowing subscribe related PUSH messages to pass through. * This allows existing code to be backward compatible and work in @@ -539,6 +540,7 @@ void redisProcessCallbacks(redisAsyncContext *ac) { /* Even if the context is subscribed, pending regular * callbacks will get a reply before pub/sub messages arrive. */ + redisCallback cb = {NULL, NULL, 0, NULL}; if (__redisShiftCallback(&ac->replies,&cb) != REDIS_OK) { /* * A spontaneous reply in a not-subscribed context can be the error @@ -562,15 +564,17 @@ void redisProcessCallbacks(redisAsyncContext *ac) { __redisAsyncDisconnect(ac); return; } - /* No more regular callbacks and no errors, the context *must* be subscribed or monitoring. */ - assert((c->flags & REDIS_SUBSCRIBED || c->flags & REDIS_MONITORING)); - if(c->flags & REDIS_SUBSCRIBED) + /* No more regular callbacks and no errors, the context *must* be subscribed. */ + assert(c->flags & REDIS_SUBSCRIBED); + if (c->flags & REDIS_SUBSCRIBED) __redisGetSubscribeCallback(ac,reply,&cb); } if (cb.fn != NULL) { __redisRunCallback(ac,&cb,reply); - c->reader->fn->freeObject(reply); + if (!(c->flags & REDIS_NO_AUTO_FREE_REPLIES)){ + c->reader->fn->freeObject(reply); + } /* Proceed with free'ing when redisAsyncFree() was called. */ if (c->flags & REDIS_FREEING) { @@ -584,6 +588,11 @@ void redisProcessCallbacks(redisAsyncContext *ac) { * doesn't know what the server will spit out over the wire. */ c->reader->fn->freeObject(reply); } + + /* If in monitor mode, repush the callback */ + if (c->flags & REDIS_MONITORING) { + __redisPushCallback(&ac->replies,&cb); + } } /* Disconnect when there was an error reading the reply */ @@ -605,7 +614,8 @@ static int __redisAsyncHandleConnect(redisAsyncContext *ac) { if (redisCheckConnectDone(c, &completed) == REDIS_ERR) { /* Error! */ - redisCheckSocketError(c); + if (redisCheckSocketError(c) == REDIS_ERR) + __redisAsyncCopyError(ac); __redisAsyncHandleConnectFailure(ac); return REDIS_ERR; } else if (completed == 1) { @@ -691,13 +701,22 @@ void redisAsyncHandleTimeout(redisAsyncContext *ac) { redisContext *c = &(ac->c); redisCallback cb; - if ((c->flags & REDIS_CONNECTED) && ac->replies.head == NULL) { - /* Nothing to do - just an idle timeout */ - return; + if ((c->flags & REDIS_CONNECTED)) { + if (ac->replies.head == NULL && ac->sub.replies.head == NULL) { + /* Nothing to do - just an idle timeout */ + return; + } + + if (!ac->c.command_timeout || + (!ac->c.command_timeout->tv_sec && !ac->c.command_timeout->tv_usec)) { + /* A belated connect timeout arriving, ignore */ + return; + } } if (!c->err) { __redisSetError(c, REDIS_ERR_TIMEOUT, "Timeout"); + __redisAsyncCopyError(ac); } if (!(c->flags & REDIS_CONNECTED) && ac->onConnect) { @@ -744,7 +763,7 @@ static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void const char *cstr, *astr; size_t clen, alen; const char *p; - hisds sname; + sds sname; int ret; /* Don't accept new commands when the connection is about to be closed. */ @@ -768,7 +787,7 @@ static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void /* Add every channel/pattern to the list of subscription callbacks. */ while ((p = nextArgument(p,&astr,&alen)) != NULL) { - sname = hi_sdsnewlen(astr,alen); + sname = sdsnewlen(astr,alen); if (sname == NULL) goto oom; @@ -786,7 +805,7 @@ static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void ret = dictReplace(cbdict,sname,&cb); - if (ret == 0) hi_sdsfree(sname); + if (ret == 0) sdsfree(sname); } } else if (strncasecmp(cstr,"unsubscribe\r\n",13) == 0) { /* It is only useful to call (P)UNSUBSCRIBE when the context is @@ -796,17 +815,19 @@ static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void /* (P)UNSUBSCRIBE does not have its own response: every channel or * pattern that is unsubscribed will receive a message. This means we * should not append a callback function for this command. */ - } else if(strncasecmp(cstr,"monitor\r\n",9) == 0) { - /* Set monitor flag and push callback */ - c->flags |= REDIS_MONITORING; - __redisPushCallback(&ac->replies,&cb); + } else if (strncasecmp(cstr,"monitor\r\n",9) == 0) { + /* Set monitor flag and push callback */ + c->flags |= REDIS_MONITORING; + if (__redisPushCallback(&ac->replies,&cb) != REDIS_OK) + goto oom; } else { - if (c->flags & REDIS_SUBSCRIBED) - /* This will likely result in an error reply, but it needs to be - * received and passed to the callback. */ - __redisPushCallback(&ac->sub.invalid,&cb); - else - __redisPushCallback(&ac->replies,&cb); + if (c->flags & REDIS_SUBSCRIBED) { + if (__redisPushCallback(&ac->sub.replies,&cb) != REDIS_OK) + goto oom; + } else { + if (__redisPushCallback(&ac->replies,&cb) != REDIS_OK) + goto oom; + } } __redisAppendCommand(c,cmd,len); @@ -817,6 +838,7 @@ static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void return REDIS_OK; oom: __redisSetError(&(ac->c), REDIS_ERR_OOM, "Out of memory"); + __redisAsyncCopyError(ac); return REDIS_ERR; } @@ -845,14 +867,14 @@ int redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata } int redisAsyncCommandArgv(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen) { - hisds cmd; - int len; + sds cmd; + long long len; int status; len = redisFormatSdsCommandArgv(&cmd,argc,argv,argvlen); if (len < 0) return REDIS_ERR; status = __redisAsyncCommand(ac,fn,privdata,cmd,len); - hi_sdsfree(cmd); + sdsfree(cmd); return status; } diff --git a/async.h b/async.h index b1d2cb263..4c65203c1 100644 --- a/async.h +++ b/async.h @@ -102,7 +102,7 @@ typedef struct redisAsyncContext { /* Subscription callbacks */ struct { - redisCallbackList invalid; + redisCallbackList replies; struct dict *channels; struct dict *patterns; } sub; diff --git a/dict.c b/dict.c index 34a33ead9..ad571818e 100644 --- a/dict.c +++ b/dict.c @@ -267,16 +267,11 @@ static dictEntry *dictFind(dict *ht, const void *key) { return NULL; } -static dictIterator *dictGetIterator(dict *ht) { - dictIterator *iter = hi_malloc(sizeof(*iter)); - if (iter == NULL) - return NULL; - +static void dictInitIterator(dictIterator *iter, dict *ht) { iter->ht = ht; iter->index = -1; iter->entry = NULL; iter->nextEntry = NULL; - return iter; } static dictEntry *dictNext(dictIterator *iter) { @@ -299,10 +294,6 @@ static dictEntry *dictNext(dictIterator *iter) { return NULL; } -static void dictReleaseIterator(dictIterator *iter) { - hi_free(iter); -} - /* ------------------------- private functions ------------------------------ */ /* Expand the hash table if needed */ diff --git a/dict.h b/dict.h index 95fcd280e..6ad0acd8d 100644 --- a/dict.h +++ b/dict.h @@ -119,8 +119,7 @@ static int dictReplace(dict *ht, void *key, void *val); static int dictDelete(dict *ht, const void *key); static void dictRelease(dict *ht); static dictEntry * dictFind(dict *ht, const void *key); -static dictIterator *dictGetIterator(dict *ht); +static void dictInitIterator(dictIterator *iter, dict *ht); static dictEntry *dictNext(dictIterator *iter); -static void dictReleaseIterator(dictIterator *iter); #endif /* __DICT_H */ diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 1d5bc56e0..49cd8d440 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -21,7 +21,7 @@ ENDIF() FIND_PATH(LIBEVENT event.h) if (LIBEVENT) - ADD_EXECUTABLE(example-libevent example-libevent) + ADD_EXECUTABLE(example-libevent example-libevent.c) TARGET_LINK_LIBRARIES(example-libevent hiredis event) ENDIF() diff --git a/examples/example-libuv.c b/examples/example-libuv.c index cbde452b9..53fd04a8e 100644 --- a/examples/example-libuv.c +++ b/examples/example-libuv.c @@ -7,18 +7,33 @@ #include #include +void debugCallback(redisAsyncContext *c, void *r, void *privdata) { + (void)privdata; //unused + redisReply *reply = r; + if (reply == NULL) { + /* The DEBUG SLEEP command will almost always fail, because we have set a 1 second timeout */ + printf("`DEBUG SLEEP` error: %s\n", c->errstr ? c->errstr : "unknown error"); + return; + } + /* Disconnect after receiving the reply of DEBUG SLEEP (which will not)*/ + redisAsyncDisconnect(c); +} + void getCallback(redisAsyncContext *c, void *r, void *privdata) { redisReply *reply = r; - if (reply == NULL) return; - printf("argv[%s]: %s\n", (char*)privdata, reply->str); + if (reply == NULL) { + printf("`GET key` error: %s\n", c->errstr ? c->errstr : "unknown error"); + return; + } + printf("`GET key` result: argv[%s]: %s\n", (char*)privdata, reply->str); - /* Disconnect after receiving the reply to GET */ - redisAsyncDisconnect(c); + /* start another request that demonstrate timeout */ + redisAsyncCommand(c, debugCallback, NULL, "DEBUG SLEEP %f", 1.5); } void connectCallback(const redisAsyncContext *c, int status) { if (status != REDIS_OK) { - printf("Error: %s\n", c->errstr); + printf("connect error: %s\n", c->errstr); return; } printf("Connected...\n"); @@ -26,7 +41,7 @@ void connectCallback(const redisAsyncContext *c, int status) { void disconnectCallback(const redisAsyncContext *c, int status) { if (status != REDIS_OK) { - printf("Error: %s\n", c->errstr); + printf("disconnect because of error: %s\n", c->errstr); return; } printf("Disconnected...\n"); @@ -49,8 +64,18 @@ int main (int argc, char **argv) { redisLibuvAttach(c,loop); redisAsyncSetConnectCallback(c,connectCallback); redisAsyncSetDisconnectCallback(c,disconnectCallback); + redisAsyncSetTimeout(c, (struct timeval){ .tv_sec = 1, .tv_usec = 0}); + + /* + In this demo, we first `set key`, then `get key` to demonstrate the basic usage of libuv adapter. + Then in `getCallback`, we start a `debug sleep` command to create 1.5 second long request. + Because we have set a 1 second timeout to the connection, the command will always fail with a + timeout error, which is shown in the `debugCallback`. + */ + redisAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1])); redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key"); + uv_run(loop, UV_RUN_DEFAULT); return 0; } diff --git a/examples/example-push.c b/examples/example-push.c index 2d4ab4dc0..6bc12055e 100644 --- a/examples/example-push.c +++ b/examples/example-push.c @@ -31,7 +31,6 @@ #include #include #include -#include #define KEY_COUNT 5 diff --git a/examples/example-ssl.c b/examples/example-ssl.c index c754177cf..b8ca44281 100644 --- a/examples/example-ssl.c +++ b/examples/example-ssl.c @@ -4,7 +4,10 @@ #include #include -#include + +#ifdef _MSC_VER +#include /* For struct timeval */ +#endif int main(int argc, char **argv) { unsigned int j; diff --git a/examples/example.c b/examples/example.c index 15dacbd18..f1b8b4a85 100644 --- a/examples/example.c +++ b/examples/example.c @@ -2,7 +2,10 @@ #include #include #include -#include + +#ifdef _MSC_VER +#include /* For struct timeval */ +#endif int main(int argc, char **argv) { unsigned int j, isunix = 0; diff --git a/fmacros.h b/fmacros.h index 3227faafd..754a53c21 100644 --- a/fmacros.h +++ b/fmacros.h @@ -1,8 +1,10 @@ #ifndef __HIREDIS_FMACRO_H #define __HIREDIS_FMACRO_H +#ifndef _AIX #define _XOPEN_SOURCE 600 #define _POSIX_C_SOURCE 200112L +#endif #if defined(__APPLE__) && defined(__MACH__) /* Enable TCP_KEEPALIVE */ diff --git a/fuzzing/format_command_fuzzer.c b/fuzzing/format_command_fuzzer.c new file mode 100644 index 000000000..91adeac58 --- /dev/null +++ b/fuzzing/format_command_fuzzer.c @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2020, Salvatore Sanfilippo + * Copyright (c) 2020, Pieter Noordhuis + * Copyright (c) 2020, Matt Stancliff , + * Jan-Erik Rediger + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Redis nor the names of its contributors may be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include "hiredis.h" + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + char *new_str, *cmd; + + if (size < 3) + return 0; + + new_str = malloc(size+1); + if (new_str == NULL) + return 0; + + memcpy(new_str, data, size); + new_str[size] = '\0'; + + redisFormatCommand(&cmd, new_str); + + if (cmd != NULL) + hi_free(cmd); + free(new_str); + return 0; +} diff --git a/hiredis.c b/hiredis.c index 38e7e950a..91086f6f6 100644 --- a/hiredis.c +++ b/hiredis.c @@ -96,6 +96,8 @@ void freeReplyObject(void *reply) { switch(r->type) { case REDIS_REPLY_INTEGER: + case REDIS_REPLY_NIL: + case REDIS_REPLY_BOOL: break; /* Nothing to free */ case REDIS_REPLY_ARRAY: case REDIS_REPLY_MAP: @@ -112,6 +114,7 @@ void freeReplyObject(void *reply) { case REDIS_REPLY_STRING: case REDIS_REPLY_DOUBLE: case REDIS_REPLY_VERB: + case REDIS_REPLY_BIGNUM: hi_free(r->str); break; } @@ -129,7 +132,8 @@ static void *createStringObject(const redisReadTask *task, char *str, size_t len assert(task->type == REDIS_REPLY_ERROR || task->type == REDIS_REPLY_STATUS || task->type == REDIS_REPLY_STRING || - task->type == REDIS_REPLY_VERB); + task->type == REDIS_REPLY_VERB || + task->type == REDIS_REPLY_BIGNUM); /* Copy string value */ if (task->type == REDIS_REPLY_VERB) { @@ -235,12 +239,14 @@ static void *createDoubleObject(const redisReadTask *task, double value, char *s * decimal string conversion artifacts. */ memcpy(r->str, str, len); r->str[len] = '\0'; + r->len = len; if (task->parent) { parent = task->parent->obj; assert(parent->type == REDIS_REPLY_ARRAY || parent->type == REDIS_REPLY_MAP || - parent->type == REDIS_REPLY_SET); + parent->type == REDIS_REPLY_SET || + parent->type == REDIS_REPLY_PUSH); parent->element[task->idx] = r; } return r; @@ -257,7 +263,8 @@ static void *createNilObject(const redisReadTask *task) { parent = task->parent->obj; assert(parent->type == REDIS_REPLY_ARRAY || parent->type == REDIS_REPLY_MAP || - parent->type == REDIS_REPLY_SET); + parent->type == REDIS_REPLY_SET || + parent->type == REDIS_REPLY_PUSH); parent->element[task->idx] = r; } return r; @@ -276,7 +283,8 @@ static void *createBoolObject(const redisReadTask *task, int bval) { parent = task->parent->obj; assert(parent->type == REDIS_REPLY_ARRAY || parent->type == REDIS_REPLY_MAP || - parent->type == REDIS_REPLY_SET); + parent->type == REDIS_REPLY_SET || + parent->type == REDIS_REPLY_PUSH); parent->element[task->idx] = r; } return r; @@ -305,7 +313,7 @@ int redisvFormatCommand(char **target, const char *format, va_list ap) { const char *c = format; char *cmd = NULL; /* final command */ int pos; /* position in final command */ - hisds curarg, newarg; /* current argument */ + sds curarg, newarg; /* current argument */ int touched = 0; /* was the current argument touched? */ char **curargv = NULL, **newargv = NULL; int argc = 0; @@ -318,7 +326,7 @@ int redisvFormatCommand(char **target, const char *format, va_list ap) { return -1; /* Build the command string accordingly to protocol */ - curarg = hi_sdsempty(); + curarg = sdsempty(); if (curarg == NULL) return -1; @@ -330,15 +338,15 @@ int redisvFormatCommand(char **target, const char *format, va_list ap) { if (newargv == NULL) goto memory_err; curargv = newargv; curargv[argc++] = curarg; - totlen += bulklen(hi_sdslen(curarg)); + totlen += bulklen(sdslen(curarg)); /* curarg is put in argv so it can be overwritten. */ - curarg = hi_sdsempty(); + curarg = sdsempty(); if (curarg == NULL) goto memory_err; touched = 0; } } else { - newarg = hi_sdscatlen(curarg,c,1); + newarg = sdscatlen(curarg,c,1); if (newarg == NULL) goto memory_err; curarg = newarg; touched = 1; @@ -355,16 +363,16 @@ int redisvFormatCommand(char **target, const char *format, va_list ap) { arg = va_arg(ap,char*); size = strlen(arg); if (size > 0) - newarg = hi_sdscatlen(curarg,arg,size); + newarg = sdscatlen(curarg,arg,size); break; case 'b': arg = va_arg(ap,char*); size = va_arg(ap,size_t); if (size > 0) - newarg = hi_sdscatlen(curarg,arg,size); + newarg = sdscatlen(curarg,arg,size); break; case '%': - newarg = hi_sdscat(curarg,"%"); + newarg = sdscat(curarg,"%"); break; default: /* Try to detect printf format */ @@ -452,7 +460,7 @@ int redisvFormatCommand(char **target, const char *format, va_list ap) { if (_l < sizeof(_format)-2) { memcpy(_format,c,_l); _format[_l] = '\0'; - newarg = hi_sdscatvprintf(curarg,_format,_cpy); + newarg = sdscatvprintf(curarg,_format,_cpy); /* Update current position (note: outer blocks * increment c twice so compensate here) */ @@ -479,9 +487,9 @@ int redisvFormatCommand(char **target, const char *format, va_list ap) { if (newargv == NULL) goto memory_err; curargv = newargv; curargv[argc++] = curarg; - totlen += bulklen(hi_sdslen(curarg)); + totlen += bulklen(sdslen(curarg)); } else { - hi_sdsfree(curarg); + sdsfree(curarg); } /* Clear curarg because it was put in curargv or was free'd. */ @@ -496,10 +504,10 @@ int redisvFormatCommand(char **target, const char *format, va_list ap) { pos = sprintf(cmd,"*%d\r\n",argc); for (j = 0; j < argc; j++) { - pos += sprintf(cmd+pos,"$%zu\r\n",hi_sdslen(curargv[j])); - memcpy(cmd+pos,curargv[j],hi_sdslen(curargv[j])); - pos += hi_sdslen(curargv[j]); - hi_sdsfree(curargv[j]); + pos += sprintf(cmd+pos,"$%zu\r\n",sdslen(curargv[j])); + memcpy(cmd+pos,curargv[j],sdslen(curargv[j])); + pos += sdslen(curargv[j]); + sdsfree(curargv[j]); cmd[pos++] = '\r'; cmd[pos++] = '\n'; } @@ -521,11 +529,11 @@ memory_err: cleanup: if (curargv) { while(argc--) - hi_sdsfree(curargv[argc]); + sdsfree(curargv[argc]); hi_free(curargv); } - hi_sdsfree(curarg); + sdsfree(curarg); hi_free(cmd); return error_type; @@ -558,19 +566,18 @@ int redisFormatCommand(char **target, const char *format, ...) { return len; } -/* Format a command according to the Redis protocol using an hisds string and - * hi_sdscatfmt for the processing of arguments. This function takes the +/* Format a command according to the Redis protocol using an sds string and + * sdscatfmt for the processing of arguments. This function takes the * number of arguments, an array with arguments and an array with their * lengths. If the latter is set to NULL, strlen will be used to compute the * argument lengths. */ -int redisFormatSdsCommandArgv(hisds *target, int argc, const char **argv, - const size_t *argvlen) +long long redisFormatSdsCommandArgv(sds *target, int argc, const char **argv, + const size_t *argvlen) { - hisds cmd, aux; - unsigned long long totlen; + sds cmd, aux; + unsigned long long totlen, len; int j; - size_t len; /* Abort on a NULL target */ if (target == NULL) @@ -584,36 +591,36 @@ int redisFormatSdsCommandArgv(hisds *target, int argc, const char **argv, } /* Use an SDS string for command construction */ - cmd = hi_sdsempty(); + cmd = sdsempty(); if (cmd == NULL) return -1; /* We already know how much storage we need */ - aux = hi_sdsMakeRoomFor(cmd, totlen); + aux = sdsMakeRoomFor(cmd, totlen); if (aux == NULL) { - hi_sdsfree(cmd); + sdsfree(cmd); return -1; } cmd = aux; /* Construct command */ - cmd = hi_sdscatfmt(cmd, "*%i\r\n", argc); + cmd = sdscatfmt(cmd, "*%i\r\n", argc); for (j=0; j < argc; j++) { len = argvlen ? argvlen[j] : strlen(argv[j]); - cmd = hi_sdscatfmt(cmd, "$%u\r\n", len); - cmd = hi_sdscatlen(cmd, argv[j], len); - cmd = hi_sdscatlen(cmd, "\r\n", sizeof("\r\n")-1); + cmd = sdscatfmt(cmd, "$%U\r\n", len); + cmd = sdscatlen(cmd, argv[j], len); + cmd = sdscatlen(cmd, "\r\n", sizeof("\r\n")-1); } - assert(hi_sdslen(cmd)==totlen); + assert(sdslen(cmd)==totlen); *target = cmd; return totlen; } -void redisFreeSdsCommand(hisds cmd) { - hi_sdsfree(cmd); +void redisFreeSdsCommand(sds cmd) { + sdsfree(cmd); } /* Format a command according to the Redis protocol. This function takes the @@ -621,11 +628,11 @@ void redisFreeSdsCommand(hisds cmd) { * lengths. If the latter is set to NULL, strlen will be used to compute the * argument lengths. */ -int redisFormatCommandArgv(char **target, int argc, const char **argv, const size_t *argvlen) { +long long redisFormatCommandArgv(char **target, int argc, const char **argv, const size_t *argvlen) { char *cmd = NULL; /* final command */ - int pos; /* position in final command */ - size_t len; - int totlen, j; + size_t pos; /* position in final command */ + size_t len, totlen; + int j; /* Abort on a NULL target */ if (target == NULL) @@ -697,7 +704,7 @@ static redisContext *redisContextInit(void) { c->funcs = &redisContextDefaultFuncs; - c->obuf = hi_sdsempty(); + c->obuf = sdsempty(); c->reader = redisReaderCreate(); c->fd = REDIS_INVALID_FD; @@ -714,7 +721,7 @@ void redisFree(redisContext *c) { return; redisNetClose(c); - hi_sdsfree(c->obuf); + sdsfree(c->obuf); redisReaderFree(c->reader); hi_free(c->tcp.host); hi_free(c->tcp.source_addr); @@ -751,10 +758,10 @@ int redisReconnect(redisContext *c) { redisNetClose(c); - hi_sdsfree(c->obuf); + sdsfree(c->obuf); redisReaderFree(c->reader); - c->obuf = hi_sdsempty(); + c->obuf = sdsempty(); c->reader = redisReaderCreate(); if (c->obuf == NULL || c->reader == NULL) { @@ -796,6 +803,9 @@ redisContext *redisConnectWithOptions(const redisOptions *options) { if (options->options & REDIS_OPT_NOAUTOFREE) { c->flags |= REDIS_NO_AUTO_FREE; } + if (options->options & REDIS_OPT_NOAUTOFREEREPLIES) { + c->flags |= REDIS_NO_AUTO_FREE_REPLIES; + } /* Set any user supplied RESP3 PUSH handler or use freeReplyObject * as a default unless specifically flagged that we don't want one. */ @@ -824,7 +834,7 @@ redisContext *redisConnectWithOptions(const redisOptions *options) { c->fd = options->endpoint.fd; c->flags |= REDIS_CONNECTED; } else { - // Unknown type - FIXME - FREE + redisFree(c); return NULL; } @@ -938,13 +948,11 @@ int redisBufferRead(redisContext *c) { return REDIS_ERR; nread = c->funcs->read(c, buf, sizeof(buf)); - if (nread > 0) { - if (redisReaderFeed(c->reader, buf, nread) != REDIS_OK) { - __redisSetError(c, c->reader->err, c->reader->errstr); - return REDIS_ERR; - } else { - } - } else if (nread < 0) { + if (nread < 0) { + return REDIS_ERR; + } + if (nread > 0 && redisReaderFeed(c->reader, buf, nread) != REDIS_OK) { + __redisSetError(c, c->reader->err, c->reader->errstr); return REDIS_ERR; } return REDIS_OK; @@ -965,22 +973,22 @@ int redisBufferWrite(redisContext *c, int *done) { if (c->err) return REDIS_ERR; - if (hi_sdslen(c->obuf) > 0) { + if (sdslen(c->obuf) > 0) { ssize_t nwritten = c->funcs->write(c); if (nwritten < 0) { return REDIS_ERR; } else if (nwritten > 0) { - if (nwritten == (ssize_t)hi_sdslen(c->obuf)) { - hi_sdsfree(c->obuf); - c->obuf = hi_sdsempty(); + if (nwritten == (ssize_t)sdslen(c->obuf)) { + sdsfree(c->obuf); + c->obuf = sdsempty(); if (c->obuf == NULL) goto oom; } else { - if (hi_sdsrange(c->obuf,nwritten,-1) < 0) goto oom; + if (sdsrange(c->obuf,nwritten,-1) < 0) goto oom; } } } - if (done != NULL) *done = (hi_sdslen(c->obuf) == 0); + if (done != NULL) *done = (sdslen(c->obuf) == 0); return REDIS_OK; oom: @@ -988,17 +996,6 @@ oom: return REDIS_ERR; } -/* Internal helper function to try and get a reply from the reader, - * or set an error in the context otherwise. */ -int redisGetReplyFromReader(redisContext *c, void **reply) { - if (redisReaderGetReply(c->reader,reply) == REDIS_ERR) { - __redisSetError(c,c->reader->err,c->reader->errstr); - return REDIS_ERR; - } - - return REDIS_OK; -} - /* Internal helper that returns 1 if the reply was a RESP3 PUSH * message and we handled it with a user-provided callback. */ static int redisHandledPushReply(redisContext *c, void *reply) { @@ -1010,12 +1007,34 @@ static int redisHandledPushReply(redisContext *c, void *reply) { return 0; } +/* Get a reply from our reader or set an error in the context. */ +int redisGetReplyFromReader(redisContext *c, void **reply) { + if (redisReaderGetReply(c->reader, reply) == REDIS_ERR) { + __redisSetError(c,c->reader->err,c->reader->errstr); + return REDIS_ERR; + } + + return REDIS_OK; +} + +/* Internal helper to get the next reply from our reader while handling + * any PUSH messages we encounter along the way. This is separate from + * redisGetReplyFromReader so as to not change its behavior. */ +static int redisNextInBandReplyFromReader(redisContext *c, void **reply) { + do { + if (redisGetReplyFromReader(c, reply) == REDIS_ERR) + return REDIS_ERR; + } while (redisHandledPushReply(c, *reply)); + + return REDIS_OK; +} + int redisGetReply(redisContext *c, void **reply) { int wdone = 0; void *aux = NULL; /* Try to read pending replies */ - if (redisGetReplyFromReader(c,&aux) == REDIS_ERR) + if (redisNextInBandReplyFromReader(c,&aux) == REDIS_ERR) return REDIS_ERR; /* For the blocking context, flush output buffer and read reply */ @@ -1031,12 +1050,8 @@ int redisGetReply(redisContext *c, void **reply) { if (redisBufferRead(c) == REDIS_ERR) return REDIS_ERR; - /* We loop here in case the user has specified a RESP3 - * PUSH handler (e.g. for client tracking). */ - do { - if (redisGetReplyFromReader(c,&aux) == REDIS_ERR) - return REDIS_ERR; - } while (redisHandledPushReply(c, aux)); + if (redisNextInBandReplyFromReader(c,&aux) == REDIS_ERR) + return REDIS_ERR; } while (aux == NULL); } @@ -1058,9 +1073,9 @@ int redisGetReply(redisContext *c, void **reply) { * the reply (or replies in pub/sub). */ int __redisAppendCommand(redisContext *c, const char *cmd, size_t len) { - hisds newbuf; + sds newbuf; - newbuf = hi_sdscatlen(c->obuf,cmd,len); + newbuf = sdscatlen(c->obuf,cmd,len); if (newbuf == NULL) { __redisSetError(c,REDIS_ERR_OOM,"Out of memory"); return REDIS_ERR; @@ -1112,8 +1127,8 @@ int redisAppendCommand(redisContext *c, const char *format, ...) { } int redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen) { - hisds cmd; - int len; + sds cmd; + long long len; len = redisFormatSdsCommandArgv(&cmd,argc,argv,argvlen); if (len == -1) { @@ -1122,11 +1137,11 @@ int redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const s } if (__redisAppendCommand(c,cmd,len) != REDIS_OK) { - hi_sdsfree(cmd); + sdsfree(cmd); return REDIS_ERR; } - hi_sdsfree(cmd); + sdsfree(cmd); return REDIS_OK; } diff --git a/hiredis.h b/hiredis.h index b597394d4..b378128b5 100644 --- a/hiredis.h +++ b/hiredis.h @@ -42,13 +42,13 @@ struct timeval; /* forward declaration */ typedef long long ssize_t; #endif #include /* uintXX_t, etc */ -#include "sds.h" /* for hisds */ +#include "sds.h" /* for sds */ #include "alloc.h" /* for allocation wrappers */ #define HIREDIS_MAJOR 1 #define HIREDIS_MINOR 0 -#define HIREDIS_PATCH 0 -#define HIREDIS_SONAME 1.0.0 +#define HIREDIS_PATCH 3 +#define HIREDIS_SONAME 1.0.3-dev /* Connection type can be blocking or non-blocking and is set in the * least significant bit of the flags field in redisContext. */ @@ -80,12 +80,18 @@ typedef long long ssize_t; /* Flag that is set when we should set SO_REUSEADDR before calling bind() */ #define REDIS_REUSEADDR 0x80 +/* Flag that is set when the async connection supports push replies. */ +#define REDIS_SUPPORTS_PUSH 0x100 + /** * Flag that indicates the user does not want the context to * be automatically freed upon error */ #define REDIS_NO_AUTO_FREE 0x200 +/* Flag that indicates the user does not want replies to be automatically freed */ +#define REDIS_NO_AUTO_FREE_REPLIES 0x400 + #define REDIS_KEEPALIVE_INTERVAL 15 /* seconds */ /* number of times we retry to connect in the case of EADDRNOTAVAIL and @@ -112,7 +118,8 @@ typedef struct redisReply { double dval; /* The double when type is REDIS_REPLY_DOUBLE */ size_t len; /* Length of string */ char *str; /* Used for REDIS_REPLY_ERROR, REDIS_REPLY_STRING - REDIS_REPLY_VERB, and REDIS_REPLY_DOUBLE (in additional to dval). */ + REDIS_REPLY_VERB, REDIS_REPLY_DOUBLE (in additional to dval), + and REDIS_REPLY_BIGNUM. */ char vtype[4]; /* Used for REDIS_REPLY_VERB, contains the null terminated 3 character content type, such as "txt". */ size_t elements; /* number of elements, for REDIS_REPLY_ARRAY */ @@ -127,10 +134,10 @@ void freeReplyObject(void *reply); /* Functions to format a command according to the protocol. */ int redisvFormatCommand(char **target, const char *format, va_list ap); int redisFormatCommand(char **target, const char *format, ...); -int redisFormatCommandArgv(char **target, int argc, const char **argv, const size_t *argvlen); -int redisFormatSdsCommandArgv(hisds *target, int argc, const char ** argv, const size_t *argvlen); +long long redisFormatCommandArgv(char **target, int argc, const char **argv, const size_t *argvlen); +long long redisFormatSdsCommandArgv(sds *target, int argc, const char ** argv, const size_t *argvlen); void redisFreeCommand(char *cmd); -void redisFreeSdsCommand(hisds cmd); +void redisFreeSdsCommand(sds cmd); enum redisConnectionType { REDIS_CONN_TCP, @@ -152,6 +159,11 @@ struct redisSsl; /* Don't automatically intercept and free RESP3 PUSH replies. */ #define REDIS_OPT_NO_PUSH_AUTOFREE 0x08 +/** + * Don't automatically free replies + */ +#define REDIS_OPT_NOAUTOFREEREPLIES 0x10 + /* In Unix systems a file descriptor is a regular signed int, with -1 * representing an invalid descriptor. In Windows it is a SOCKET * (32- or 64-bit unsigned integer depending on the architecture), where @@ -255,7 +267,7 @@ typedef struct redisContext { } unix_sock; /* For non-blocking connect */ - struct sockadr *saddr; + struct sockaddr *saddr; size_t addrlen; /* Optional data and corresponding destructor users can use to provide diff --git a/hiredis.targets b/hiredis.targets new file mode 100644 index 000000000..effd8a561 --- /dev/null +++ b/hiredis.targets @@ -0,0 +1,11 @@ + + + + + $(MSBuildThisFileDirectory)\..\..\include;%(AdditionalIncludeDirectories) + + + $(MSBuildThisFileDirectory)\..\..\lib;%(AdditionalLibraryDirectories) + + + \ No newline at end of file diff --git a/hiredis_ssl.h b/hiredis_ssl.h index 604efe0c1..e3d3e1cf5 100644 --- a/hiredis_ssl.h +++ b/hiredis_ssl.h @@ -56,7 +56,9 @@ typedef enum { REDIS_SSL_CTX_CERT_KEY_REQUIRED, /* Client cert and key must both be specified or skipped */ REDIS_SSL_CTX_CA_CERT_LOAD_FAILED, /* Failed to load CA Certificate or CA Path */ REDIS_SSL_CTX_CLIENT_CERT_LOAD_FAILED, /* Failed to load client certificate */ - REDIS_SSL_CTX_PRIVATE_KEY_LOAD_FAILED /* Failed to load private key */ + REDIS_SSL_CTX_PRIVATE_KEY_LOAD_FAILED, /* Failed to load private key */ + REDIS_SSL_CTX_OS_CERTSTORE_OPEN_FAILED, /* Failed to open system certifcate store */ + REDIS_SSL_CTX_OS_CERT_ADD_FAILED /* Failed to add CA certificates obtained from system to the SSL context */ } redisSSLContextError; /** diff --git a/net.c b/net.c index 88f9aff25..c6b0e5d8e 100644 --- a/net.c +++ b/net.c @@ -80,7 +80,7 @@ ssize_t redisNetRead(redisContext *c, char *buf, size_t bufcap) { } ssize_t redisNetWrite(redisContext *c) { - ssize_t nwritten = send(c->fd, c->obuf, hi_sdslen(c->obuf), 0); + ssize_t nwritten = send(c->fd, c->obuf, sdslen(c->obuf), 0); if (nwritten < 0) { if ((errno == EWOULDBLOCK && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) { /* Try again later */ diff --git a/read.c b/read.c index 682b9a6b9..de62b9ab0 100644 --- a/read.c +++ b/read.c @@ -59,7 +59,7 @@ static void __redisReaderSetError(redisReader *r, int type, const char *str) { } /* Clear input buffer on errors. */ - hi_sdsfree(r->buf); + sdsfree(r->buf); r->buf = NULL; r->pos = r->len = 0; @@ -123,29 +123,28 @@ static char *readBytes(redisReader *r, unsigned int bytes) { /* Find pointer to \r\n. */ static char *seekNewline(char *s, size_t len) { - int pos = 0; - int _len = len-1; + char *ret; - /* Position should be < len-1 because the character at "pos" should be - * followed by a \n. Note that strchr cannot be used because it doesn't - * allow to search a limited length and the buffer that is being searched - * might not have a trailing NULL character. */ - while (pos < _len) { - while(pos < _len && s[pos] != '\r') pos++; - if (pos==_len) { - /* Not found. */ - return NULL; - } else { - if (s[pos+1] == '\n') { - /* Found. */ - return s+pos; - } else { - /* Continue searching. */ - pos++; - } + /* We cannot match with fewer than 2 bytes */ + if (len < 2) + return NULL; + + /* Search up to len - 1 characters */ + len--; + + /* Look for the \r */ + while ((ret = memchr(s, '\r', len)) != NULL) { + if (ret[1] == '\n') { + /* Found. */ + break; } + /* Continue searching. */ + ret++; + len -= ret - s; + s = ret; } - return NULL; + + return ret; } /* Convert a string into a long long. Returns REDIS_OK if the string could be @@ -274,60 +273,104 @@ static int processLineItem(redisReader *r) { if ((p = readLine(r,&len)) != NULL) { if (cur->type == REDIS_REPLY_INTEGER) { + long long v; + + if (string2ll(p, len, &v) == REDIS_ERR) { + __redisReaderSetError(r,REDIS_ERR_PROTOCOL, + "Bad integer value"); + return REDIS_ERR; + } + if (r->fn && r->fn->createInteger) { - long long v; - if (string2ll(p, len, &v) == REDIS_ERR) { - __redisReaderSetError(r,REDIS_ERR_PROTOCOL, - "Bad integer value"); - return REDIS_ERR; - } obj = r->fn->createInteger(cur,v); } else { obj = (void*)REDIS_REPLY_INTEGER; } } else if (cur->type == REDIS_REPLY_DOUBLE) { - if (r->fn && r->fn->createDouble) { - char buf[326], *eptr; - double d; + char buf[326], *eptr; + double d; - if ((size_t)len >= sizeof(buf)) { + if ((size_t)len >= sizeof(buf)) { + __redisReaderSetError(r,REDIS_ERR_PROTOCOL, + "Double value is too large"); + return REDIS_ERR; + } + + memcpy(buf,p,len); + buf[len] = '\0'; + + if (len == 3 && strcasecmp(buf,"inf") == 0) { + d = INFINITY; /* Positive infinite. */ + } else if (len == 4 && strcasecmp(buf,"-inf") == 0) { + d = -INFINITY; /* Negative infinite. */ + } else { + d = strtod((char*)buf,&eptr); + /* RESP3 only allows "inf", "-inf", and finite values, while + * strtod() allows other variations on infinity, NaN, + * etc. We explicity handle our two allowed infinite cases + * above, so strtod() should only result in finite values. */ + if (buf[0] == '\0' || eptr != &buf[len] || !isfinite(d)) { __redisReaderSetError(r,REDIS_ERR_PROTOCOL, - "Double value is too large"); + "Bad double value"); return REDIS_ERR; } + } - memcpy(buf,p,len); - buf[len] = '\0'; - - if (strcasecmp(buf,",inf") == 0) { - d = INFINITY; /* Positive infinite. */ - } else if (strcasecmp(buf,",-inf") == 0) { - d = -INFINITY; /* Negative infinite. */ - } else { - d = strtod((char*)buf,&eptr); - if (buf[0] == '\0' || eptr[0] != '\0' || isnan(d)) { - __redisReaderSetError(r,REDIS_ERR_PROTOCOL, - "Bad double value"); - return REDIS_ERR; - } - } + if (r->fn && r->fn->createDouble) { obj = r->fn->createDouble(cur,d,buf,len); } else { obj = (void*)REDIS_REPLY_DOUBLE; } } else if (cur->type == REDIS_REPLY_NIL) { + if (len != 0) { + __redisReaderSetError(r,REDIS_ERR_PROTOCOL, + "Bad nil value"); + return REDIS_ERR; + } + if (r->fn && r->fn->createNil) obj = r->fn->createNil(cur); else obj = (void*)REDIS_REPLY_NIL; } else if (cur->type == REDIS_REPLY_BOOL) { - int bval = p[0] == 't' || p[0] == 'T'; + int bval; + + if (len != 1 || !strchr("tTfF", p[0])) { + __redisReaderSetError(r,REDIS_ERR_PROTOCOL, + "Bad bool value"); + return REDIS_ERR; + } + + bval = p[0] == 't' || p[0] == 'T'; if (r->fn && r->fn->createBool) obj = r->fn->createBool(cur,bval); else obj = (void*)REDIS_REPLY_BOOL; + } else if (cur->type == REDIS_REPLY_BIGNUM) { + /* Ensure all characters are decimal digits (with possible leading + * minus sign). */ + for (int i = 0; i < len; i++) { + /* XXX Consider: Allow leading '+'? Error on leading '0's? */ + if (i == 0 && p[0] == '-') continue; + if (p[i] < '0' || p[i] > '9') { + __redisReaderSetError(r,REDIS_ERR_PROTOCOL, + "Bad bignum value"); + return REDIS_ERR; + } + } + if (r->fn && r->fn->createString) + obj = r->fn->createString(cur,p,len); + else + obj = (void*)REDIS_REPLY_BIGNUM; } else { /* Type will be error or status. */ + for (int i = 0; i < len; i++) { + if (p[i] == '\r' || p[i] == '\n') { + __redisReaderSetError(r,REDIS_ERR_PROTOCOL, + "Bad simple string value"); + return REDIS_ERR; + } + } if (r->fn && r->fn->createString) obj = r->fn->createString(cur,p,len); else @@ -453,7 +496,6 @@ static int processAggregateItem(redisReader *r) { long long elements; int root = 0, len; - /* Set error for nested multi bulks with depth > 7 */ if (r->ridx == r->tasks - 1) { if (redisReaderGrow(r) == REDIS_ERR) return REDIS_ERR; @@ -569,6 +611,9 @@ static int processItem(redisReader *r) { case '>': cur->type = REDIS_REPLY_PUSH; break; + case '(': + cur->type = REDIS_REPLY_BIGNUM; + break; default: __redisReaderSetErrorProtocolByte(r,*p); return REDIS_ERR; @@ -587,6 +632,7 @@ static int processItem(redisReader *r) { case REDIS_REPLY_DOUBLE: case REDIS_REPLY_NIL: case REDIS_REPLY_BOOL: + case REDIS_REPLY_BIGNUM: return processLineItem(r); case REDIS_REPLY_STRING: case REDIS_REPLY_VERB: @@ -609,7 +655,7 @@ redisReader *redisReaderCreateWithFunctions(redisReplyObjectFunctions *fn) { if (r == NULL) return NULL; - r->buf = hi_sdsempty(); + r->buf = sdsempty(); if (r->buf == NULL) goto oom; @@ -650,12 +696,12 @@ void redisReaderFree(redisReader *r) { hi_free(r->task); } - hi_sdsfree(r->buf); + sdsfree(r->buf); hi_free(r); } int redisReaderFeed(redisReader *r, const char *buf, size_t len) { - hisds newbuf; + sds newbuf; /* Return early when this reader is in an erroneous state. */ if (r->err) @@ -664,19 +710,19 @@ int redisReaderFeed(redisReader *r, const char *buf, size_t len) { /* Copy the provided buffer. */ if (buf != NULL && len >= 1) { /* Destroy internal buffer when it is empty and is quite large. */ - if (r->len == 0 && r->maxbuf != 0 && hi_sdsavail(r->buf) > r->maxbuf) { - hi_sdsfree(r->buf); - r->buf = hi_sdsempty(); + if (r->len == 0 && r->maxbuf != 0 && sdsavail(r->buf) > r->maxbuf) { + sdsfree(r->buf); + r->buf = sdsempty(); if (r->buf == 0) goto oom; r->pos = 0; } - newbuf = hi_sdscatlen(r->buf,buf,len); + newbuf = sdscatlen(r->buf,buf,len); if (newbuf == NULL) goto oom; r->buf = newbuf; - r->len = hi_sdslen(r->buf); + r->len = sdslen(r->buf); } return REDIS_OK; @@ -721,9 +767,9 @@ int redisReaderGetReply(redisReader *r, void **reply) { /* Discard part of the buffer when we've consumed at least 1k, to avoid * doing unnecessary calls to memmove() in sds.c. */ if (r->pos >= 1024) { - if (hi_sdsrange(r->buf,r->pos,-1) < 0) return REDIS_ERR; + if (sdsrange(r->buf,r->pos,-1) < 0) return REDIS_ERR; r->pos = 0; - r->len = hi_sdslen(r->buf); + r->len = sdslen(r->buf); } /* Emit a reply when there is one. */ diff --git a/sds.c b/sds.c index 675e7649f..35baa057e 100644 --- a/sds.c +++ b/sds.c @@ -40,90 +40,90 @@ #include "sds.h" #include "sdsalloc.h" -static inline int hi_sdsHdrSize(char type) { - switch(type&HI_SDS_TYPE_MASK) { - case HI_SDS_TYPE_5: - return sizeof(struct hisdshdr5); - case HI_SDS_TYPE_8: - return sizeof(struct hisdshdr8); - case HI_SDS_TYPE_16: - return sizeof(struct hisdshdr16); - case HI_SDS_TYPE_32: - return sizeof(struct hisdshdr32); - case HI_SDS_TYPE_64: - return sizeof(struct hisdshdr64); +static inline int sdsHdrSize(char type) { + switch(type&SDS_TYPE_MASK) { + case SDS_TYPE_5: + return sizeof(struct sdshdr5); + case SDS_TYPE_8: + return sizeof(struct sdshdr8); + case SDS_TYPE_16: + return sizeof(struct sdshdr16); + case SDS_TYPE_32: + return sizeof(struct sdshdr32); + case SDS_TYPE_64: + return sizeof(struct sdshdr64); } return 0; } -static inline char hi_sdsReqType(size_t string_size) { +static inline char sdsReqType(size_t string_size) { if (string_size < 32) - return HI_SDS_TYPE_5; + return SDS_TYPE_5; if (string_size < 0xff) - return HI_SDS_TYPE_8; + return SDS_TYPE_8; if (string_size < 0xffff) - return HI_SDS_TYPE_16; + return SDS_TYPE_16; if (string_size < 0xffffffff) - return HI_SDS_TYPE_32; - return HI_SDS_TYPE_64; + return SDS_TYPE_32; + return SDS_TYPE_64; } -/* Create a new hisds string with the content specified by the 'init' pointer +/* Create a new sds string with the content specified by the 'init' pointer * and 'initlen'. * If NULL is used for 'init' the string is initialized with zero bytes. * - * The string is always null-termined (all the hisds strings are, always) so - * even if you create an hisds string with: + * The string is always null-terminated (all the sds strings are, always) so + * even if you create an sds string with: * - * mystring = hi_sdsnewlen("abc",3); + * mystring = sdsnewlen("abc",3); * * You can print the string with printf() as there is an implicit \0 at the * end of the string. However the string is binary safe and can contain - * \0 characters in the middle, as the length is stored in the hisds header. */ -hisds hi_sdsnewlen(const void *init, size_t initlen) { + * \0 characters in the middle, as the length is stored in the sds header. */ +sds sdsnewlen(const void *init, size_t initlen) { void *sh; - hisds s; - char type = hi_sdsReqType(initlen); + sds s; + char type = sdsReqType(initlen); /* Empty strings are usually created in order to append. Use type 8 * since type 5 is not good at this. */ - if (type == HI_SDS_TYPE_5 && initlen == 0) type = HI_SDS_TYPE_8; - int hdrlen = hi_sdsHdrSize(type); + if (type == SDS_TYPE_5 && initlen == 0) type = SDS_TYPE_8; + int hdrlen = sdsHdrSize(type); unsigned char *fp; /* flags pointer. */ - sh = hi_s_malloc(hdrlen+initlen+1); + sh = s_malloc(hdrlen+initlen+1); if (sh == NULL) return NULL; if (!init) memset(sh, 0, hdrlen+initlen+1); s = (char*)sh+hdrlen; fp = ((unsigned char*)s)-1; switch(type) { - case HI_SDS_TYPE_5: { - *fp = type | (initlen << HI_SDS_TYPE_BITS); + case SDS_TYPE_5: { + *fp = type | (initlen << SDS_TYPE_BITS); break; } - case HI_SDS_TYPE_8: { - HI_SDS_HDR_VAR(8,s); + case SDS_TYPE_8: { + SDS_HDR_VAR(8,s); sh->len = initlen; sh->alloc = initlen; *fp = type; break; } - case HI_SDS_TYPE_16: { - HI_SDS_HDR_VAR(16,s); + case SDS_TYPE_16: { + SDS_HDR_VAR(16,s); sh->len = initlen; sh->alloc = initlen; *fp = type; break; } - case HI_SDS_TYPE_32: { - HI_SDS_HDR_VAR(32,s); + case SDS_TYPE_32: { + SDS_HDR_VAR(32,s); sh->len = initlen; sh->alloc = initlen; *fp = type; break; } - case HI_SDS_TYPE_64: { - HI_SDS_HDR_VAR(64,s); + case SDS_TYPE_64: { + SDS_HDR_VAR(64,s); sh->len = initlen; sh->alloc = initlen; *fp = type; @@ -136,164 +136,164 @@ hisds hi_sdsnewlen(const void *init, size_t initlen) { return s; } -/* Create an empty (zero length) hisds string. Even in this case the string +/* Create an empty (zero length) sds string. Even in this case the string * always has an implicit null term. */ -hisds hi_sdsempty(void) { - return hi_sdsnewlen("",0); +sds sdsempty(void) { + return sdsnewlen("",0); } -/* Create a new hisds string starting from a null terminated C string. */ -hisds hi_sdsnew(const char *init) { +/* Create a new sds string starting from a null terminated C string. */ +sds sdsnew(const char *init) { size_t initlen = (init == NULL) ? 0 : strlen(init); - return hi_sdsnewlen(init, initlen); + return sdsnewlen(init, initlen); } -/* Duplicate an hisds string. */ -hisds hi_sdsdup(const hisds s) { - return hi_sdsnewlen(s, hi_sdslen(s)); +/* Duplicate an sds string. */ +sds sdsdup(const sds s) { + return sdsnewlen(s, sdslen(s)); } -/* Free an hisds string. No operation is performed if 's' is NULL. */ -void hi_sdsfree(hisds s) { +/* Free an sds string. No operation is performed if 's' is NULL. */ +void sdsfree(sds s) { if (s == NULL) return; - hi_s_free((char*)s-hi_sdsHdrSize(s[-1])); + s_free((char*)s-sdsHdrSize(s[-1])); } -/* Set the hisds string length to the length as obtained with strlen(), so +/* Set the sds string length to the length as obtained with strlen(), so * considering as content only up to the first null term character. * - * This function is useful when the hisds string is hacked manually in some + * This function is useful when the sds string is hacked manually in some * way, like in the following example: * - * s = hi_sdsnew("foobar"); + * s = sdsnew("foobar"); * s[2] = '\0'; - * hi_sdsupdatelen(s); - * printf("%d\n", hi_sdslen(s)); + * sdsupdatelen(s); + * printf("%d\n", sdslen(s)); * - * The output will be "2", but if we comment out the call to hi_sdsupdatelen() + * The output will be "2", but if we comment out the call to sdsupdatelen() * the output will be "6" as the string was modified but the logical length * remains 6 bytes. */ -void hi_sdsupdatelen(hisds s) { +void sdsupdatelen(sds s) { int reallen = strlen(s); - hi_sdssetlen(s, reallen); + sdssetlen(s, reallen); } -/* Modify an hisds string in-place to make it empty (zero length). +/* Modify an sds string in-place to make it empty (zero length). * However all the existing buffer is not discarded but set as free space * so that next append operations will not require allocations up to the * number of bytes previously available. */ -void hi_sdsclear(hisds s) { - hi_sdssetlen(s, 0); +void sdsclear(sds s) { + sdssetlen(s, 0); s[0] = '\0'; } -/* Enlarge the free space at the end of the hisds string so that the caller +/* Enlarge the free space at the end of the sds string so that the caller * is sure that after calling this function can overwrite up to addlen * bytes after the end of the string, plus one more byte for nul term. * - * Note: this does not change the *length* of the hisds string as returned - * by hi_sdslen(), but only the free buffer space we have. */ -hisds hi_sdsMakeRoomFor(hisds s, size_t addlen) { + * Note: this does not change the *length* of the sds string as returned + * by sdslen(), but only the free buffer space we have. */ +sds sdsMakeRoomFor(sds s, size_t addlen) { void *sh, *newsh; - size_t avail = hi_sdsavail(s); + size_t avail = sdsavail(s); size_t len, newlen; - char type, oldtype = s[-1] & HI_SDS_TYPE_MASK; + char type, oldtype = s[-1] & SDS_TYPE_MASK; int hdrlen; /* Return ASAP if there is enough space left. */ if (avail >= addlen) return s; - len = hi_sdslen(s); - sh = (char*)s-hi_sdsHdrSize(oldtype); + len = sdslen(s); + sh = (char*)s-sdsHdrSize(oldtype); newlen = (len+addlen); - if (newlen < HI_SDS_MAX_PREALLOC) + if (newlen < SDS_MAX_PREALLOC) newlen *= 2; else - newlen += HI_SDS_MAX_PREALLOC; + newlen += SDS_MAX_PREALLOC; - type = hi_sdsReqType(newlen); + type = sdsReqType(newlen); /* Don't use type 5: the user is appending to the string and type 5 is - * not able to remember empty space, so hi_sdsMakeRoomFor() must be called + * not able to remember empty space, so sdsMakeRoomFor() must be called * at every appending operation. */ - if (type == HI_SDS_TYPE_5) type = HI_SDS_TYPE_8; + if (type == SDS_TYPE_5) type = SDS_TYPE_8; - hdrlen = hi_sdsHdrSize(type); + hdrlen = sdsHdrSize(type); if (oldtype==type) { - newsh = hi_s_realloc(sh, hdrlen+newlen+1); + newsh = s_realloc(sh, hdrlen+newlen+1); if (newsh == NULL) return NULL; s = (char*)newsh+hdrlen; } else { /* Since the header size changes, need to move the string forward, * and can't use realloc */ - newsh = hi_s_malloc(hdrlen+newlen+1); + newsh = s_malloc(hdrlen+newlen+1); if (newsh == NULL) return NULL; memcpy((char*)newsh+hdrlen, s, len+1); - hi_s_free(sh); + s_free(sh); s = (char*)newsh+hdrlen; s[-1] = type; - hi_sdssetlen(s, len); + sdssetlen(s, len); } - hi_sdssetalloc(s, newlen); + sdssetalloc(s, newlen); return s; } -/* Reallocate the hisds string so that it has no free space at the end. The +/* Reallocate the sds string so that it has no free space at the end. The * contained string remains not altered, but next concatenation operations * will require a reallocation. * - * After the call, the passed hisds string is no longer valid and all the + * After the call, the passed sds string is no longer valid and all the * references must be substituted with the new pointer returned by the call. */ -hisds hi_sdsRemoveFreeSpace(hisds s) { +sds sdsRemoveFreeSpace(sds s) { void *sh, *newsh; - char type, oldtype = s[-1] & HI_SDS_TYPE_MASK; + char type, oldtype = s[-1] & SDS_TYPE_MASK; int hdrlen; - size_t len = hi_sdslen(s); - sh = (char*)s-hi_sdsHdrSize(oldtype); + size_t len = sdslen(s); + sh = (char*)s-sdsHdrSize(oldtype); - type = hi_sdsReqType(len); - hdrlen = hi_sdsHdrSize(type); + type = sdsReqType(len); + hdrlen = sdsHdrSize(type); if (oldtype==type) { - newsh = hi_s_realloc(sh, hdrlen+len+1); + newsh = s_realloc(sh, hdrlen+len+1); if (newsh == NULL) return NULL; s = (char*)newsh+hdrlen; } else { - newsh = hi_s_malloc(hdrlen+len+1); + newsh = s_malloc(hdrlen+len+1); if (newsh == NULL) return NULL; memcpy((char*)newsh+hdrlen, s, len+1); - hi_s_free(sh); + s_free(sh); s = (char*)newsh+hdrlen; s[-1] = type; - hi_sdssetlen(s, len); + sdssetlen(s, len); } - hi_sdssetalloc(s, len); + sdssetalloc(s, len); return s; } -/* Return the total size of the allocation of the specifed hisds string, +/* Return the total size of the allocation of the specifed sds string, * including: - * 1) The hisds header before the pointer. + * 1) The sds header before the pointer. * 2) The string. * 3) The free buffer at the end if any. * 4) The implicit null term. */ -size_t hi_sdsAllocSize(hisds s) { - size_t alloc = hi_sdsalloc(s); - return hi_sdsHdrSize(s[-1])+alloc+1; +size_t sdsAllocSize(sds s) { + size_t alloc = sdsalloc(s); + return sdsHdrSize(s[-1])+alloc+1; } /* Return the pointer of the actual SDS allocation (normally SDS strings * are referenced by the start of the string buffer). */ -void *hi_sdsAllocPtr(hisds s) { - return (void*) (s-hi_sdsHdrSize(s[-1])); +void *sdsAllocPtr(sds s) { + return (void*) (s-sdsHdrSize(s[-1])); } -/* Increment the hisds length and decrements the left free space at the +/* Increment the sds length and decrements the left free space at the * end of the string according to 'incr'. Also set the null term * in the new end of the string. * * This function is used in order to fix the string length after the - * user calls hi_sdsMakeRoomFor(), writes something after the end of + * user calls sdsMakeRoomFor(), writes something after the end of * the current string, and finally needs to set the new length. * * Note: it is possible to use a negative increment in order to @@ -301,48 +301,48 @@ void *hi_sdsAllocPtr(hisds s) { * * Usage example: * - * Using hi_sdsIncrLen() and hi_sdsMakeRoomFor() it is possible to mount the + * Using sdsIncrLen() and sdsMakeRoomFor() it is possible to mount the * following schema, to cat bytes coming from the kernel to the end of an - * hisds string without copying into an intermediate buffer: + * sds string without copying into an intermediate buffer: * - * oldlen = hi_hi_sdslen(s); - * s = hi_sdsMakeRoomFor(s, BUFFER_SIZE); + * oldlen = sdslen(s); + * s = sdsMakeRoomFor(s, BUFFER_SIZE); * nread = read(fd, s+oldlen, BUFFER_SIZE); * ... check for nread <= 0 and handle it ... - * hi_sdsIncrLen(s, nread); + * sdsIncrLen(s, nread); */ -void hi_sdsIncrLen(hisds s, int incr) { +void sdsIncrLen(sds s, int incr) { unsigned char flags = s[-1]; size_t len; - switch(flags&HI_SDS_TYPE_MASK) { - case HI_SDS_TYPE_5: { + switch(flags&SDS_TYPE_MASK) { + case SDS_TYPE_5: { unsigned char *fp = ((unsigned char*)s)-1; - unsigned char oldlen = HI_SDS_TYPE_5_LEN(flags); + unsigned char oldlen = SDS_TYPE_5_LEN(flags); assert((incr > 0 && oldlen+incr < 32) || (incr < 0 && oldlen >= (unsigned int)(-incr))); - *fp = HI_SDS_TYPE_5 | ((oldlen+incr) << HI_SDS_TYPE_BITS); + *fp = SDS_TYPE_5 | ((oldlen+incr) << SDS_TYPE_BITS); len = oldlen+incr; break; } - case HI_SDS_TYPE_8: { - HI_SDS_HDR_VAR(8,s); + case SDS_TYPE_8: { + SDS_HDR_VAR(8,s); assert((incr >= 0 && sh->alloc-sh->len >= incr) || (incr < 0 && sh->len >= (unsigned int)(-incr))); len = (sh->len += incr); break; } - case HI_SDS_TYPE_16: { - HI_SDS_HDR_VAR(16,s); + case SDS_TYPE_16: { + SDS_HDR_VAR(16,s); assert((incr >= 0 && sh->alloc-sh->len >= incr) || (incr < 0 && sh->len >= (unsigned int)(-incr))); len = (sh->len += incr); break; } - case HI_SDS_TYPE_32: { - HI_SDS_HDR_VAR(32,s); + case SDS_TYPE_32: { + SDS_HDR_VAR(32,s); assert((incr >= 0 && sh->alloc-sh->len >= (unsigned int)incr) || (incr < 0 && sh->len >= (unsigned int)(-incr))); len = (sh->len += incr); break; } - case HI_SDS_TYPE_64: { - HI_SDS_HDR_VAR(64,s); + case SDS_TYPE_64: { + SDS_HDR_VAR(64,s); assert((incr >= 0 && sh->alloc-sh->len >= (uint64_t)incr) || (incr < 0 && sh->len >= (uint64_t)(-incr))); len = (sh->len += incr); break; @@ -352,83 +352,83 @@ void hi_sdsIncrLen(hisds s, int incr) { s[len] = '\0'; } -/* Grow the hisds to have the specified length. Bytes that were not part of - * the original length of the hisds will be set to zero. +/* Grow the sds to have the specified length. Bytes that were not part of + * the original length of the sds will be set to zero. * * if the specified length is smaller than the current length, no operation * is performed. */ -hisds hi_sdsgrowzero(hisds s, size_t len) { - size_t curlen = hi_sdslen(s); +sds sdsgrowzero(sds s, size_t len) { + size_t curlen = sdslen(s); if (len <= curlen) return s; - s = hi_sdsMakeRoomFor(s,len-curlen); + s = sdsMakeRoomFor(s,len-curlen); if (s == NULL) return NULL; /* Make sure added region doesn't contain garbage */ memset(s+curlen,0,(len-curlen+1)); /* also set trailing \0 byte */ - hi_sdssetlen(s, len); + sdssetlen(s, len); return s; } /* Append the specified binary-safe string pointed by 't' of 'len' bytes to the - * end of the specified hisds string 's'. + * end of the specified sds string 's'. * - * After the call, the passed hisds string is no longer valid and all the + * After the call, the passed sds string is no longer valid and all the * references must be substituted with the new pointer returned by the call. */ -hisds hi_sdscatlen(hisds s, const void *t, size_t len) { - size_t curlen = hi_sdslen(s); +sds sdscatlen(sds s, const void *t, size_t len) { + size_t curlen = sdslen(s); - s = hi_sdsMakeRoomFor(s,len); + s = sdsMakeRoomFor(s,len); if (s == NULL) return NULL; memcpy(s+curlen, t, len); - hi_sdssetlen(s, curlen+len); + sdssetlen(s, curlen+len); s[curlen+len] = '\0'; return s; } -/* Append the specified null termianted C string to the hisds string 's'. +/* Append the specified null termianted C string to the sds string 's'. * - * After the call, the passed hisds string is no longer valid and all the + * After the call, the passed sds string is no longer valid and all the * references must be substituted with the new pointer returned by the call. */ -hisds hi_sdscat(hisds s, const char *t) { - return hi_sdscatlen(s, t, strlen(t)); +sds sdscat(sds s, const char *t) { + return sdscatlen(s, t, strlen(t)); } -/* Append the specified hisds 't' to the existing hisds 's'. +/* Append the specified sds 't' to the existing sds 's'. * - * After the call, the modified hisds string is no longer valid and all the + * After the call, the modified sds string is no longer valid and all the * references must be substituted with the new pointer returned by the call. */ -hisds hi_sdscatsds(hisds s, const hisds t) { - return hi_sdscatlen(s, t, hi_sdslen(t)); +sds sdscatsds(sds s, const sds t) { + return sdscatlen(s, t, sdslen(t)); } -/* Destructively modify the hisds string 's' to hold the specified binary +/* Destructively modify the sds string 's' to hold the specified binary * safe string pointed by 't' of length 'len' bytes. */ -hisds hi_sdscpylen(hisds s, const char *t, size_t len) { - if (hi_sdsalloc(s) < len) { - s = hi_sdsMakeRoomFor(s,len-hi_sdslen(s)); +sds sdscpylen(sds s, const char *t, size_t len) { + if (sdsalloc(s) < len) { + s = sdsMakeRoomFor(s,len-sdslen(s)); if (s == NULL) return NULL; } memcpy(s, t, len); s[len] = '\0'; - hi_sdssetlen(s, len); + sdssetlen(s, len); return s; } -/* Like hi_sdscpylen() but 't' must be a null-termined string so that the length +/* Like sdscpylen() but 't' must be a null-terminated string so that the length * of the string is obtained with strlen(). */ -hisds hi_sdscpy(hisds s, const char *t) { - return hi_sdscpylen(s, t, strlen(t)); +sds sdscpy(sds s, const char *t) { + return sdscpylen(s, t, strlen(t)); } -/* Helper for hi_sdscatlonglong() doing the actual number -> string +/* Helper for sdscatlonglong() doing the actual number -> string * conversion. 's' must point to a string with room for at least - * HI_SDS_LLSTR_SIZE bytes. + * SDS_LLSTR_SIZE bytes. * * The function returns the length of the null-terminated string * representation stored at 's'. */ -#define HI_SDS_LLSTR_SIZE 21 -int hi_sdsll2str(char *s, long long value) { +#define SDS_LLSTR_SIZE 21 +int sdsll2str(char *s, long long value) { char *p, aux; unsigned long long v; size_t l; @@ -459,8 +459,8 @@ int hi_sdsll2str(char *s, long long value) { return l; } -/* Identical hi_sdsll2str(), but for unsigned long long type. */ -int hi_sdsull2str(char *s, unsigned long long v) { +/* Identical sdsll2str(), but for unsigned long long type. */ +int sdsull2str(char *s, unsigned long long v) { char *p, aux; size_t l; @@ -488,19 +488,19 @@ int hi_sdsull2str(char *s, unsigned long long v) { return l; } -/* Create an hisds string from a long long value. It is much faster than: +/* Create an sds string from a long long value. It is much faster than: * - * hi_sdscatprintf(hi_sdsempty(),"%lld\n", value); + * sdscatprintf(sdsempty(),"%lld\n", value); */ -hisds hi_sdsfromlonglong(long long value) { - char buf[HI_SDS_LLSTR_SIZE]; - int len = hi_sdsll2str(buf,value); +sds sdsfromlonglong(long long value) { + char buf[SDS_LLSTR_SIZE]; + int len = sdsll2str(buf,value); - return hi_sdsnewlen(buf,len); + return sdsnewlen(buf,len); } -/* Like hi_sdscatprintf() but gets va_list instead of being variadic. */ -hisds hi_sdscatvprintf(hisds s, const char *fmt, va_list ap) { +/* Like sdscatprintf() but gets va_list instead of being variadic. */ +sds sdscatvprintf(sds s, const char *fmt, va_list ap) { va_list cpy; char staticbuf[1024], *buf = staticbuf, *t; size_t buflen = strlen(fmt)*2; @@ -508,7 +508,7 @@ hisds hi_sdscatvprintf(hisds s, const char *fmt, va_list ap) { /* We try to start using a static buffer for speed. * If not possible we revert to heap allocation. */ if (buflen > sizeof(staticbuf)) { - buf = hi_s_malloc(buflen); + buf = s_malloc(buflen); if (buf == NULL) return NULL; } else { buflen = sizeof(staticbuf); @@ -522,9 +522,9 @@ hisds hi_sdscatvprintf(hisds s, const char *fmt, va_list ap) { vsnprintf(buf, buflen, fmt, cpy); va_end(cpy); if (buf[buflen-2] != '\0') { - if (buf != staticbuf) hi_s_free(buf); + if (buf != staticbuf) s_free(buf); buflen *= 2; - buf = hi_s_malloc(buflen); + buf = s_malloc(buflen); if (buf == NULL) return NULL; continue; } @@ -532,39 +532,39 @@ hisds hi_sdscatvprintf(hisds s, const char *fmt, va_list ap) { } /* Finally concat the obtained string to the SDS string and return it. */ - t = hi_sdscat(s, buf); - if (buf != staticbuf) hi_s_free(buf); + t = sdscat(s, buf); + if (buf != staticbuf) s_free(buf); return t; } -/* Append to the hisds string 's' a string obtained using printf-alike format +/* Append to the sds string 's' a string obtained using printf-alike format * specifier. * - * After the call, the modified hisds string is no longer valid and all the + * After the call, the modified sds string is no longer valid and all the * references must be substituted with the new pointer returned by the call. * * Example: * - * s = hi_sdsnew("Sum is: "); - * s = hi_sdscatprintf(s,"%d+%d = %d",a,b,a+b). + * s = sdsnew("Sum is: "); + * s = sdscatprintf(s,"%d+%d = %d",a,b,a+b). * * Often you need to create a string from scratch with the printf-alike - * format. When this is the need, just use hi_sdsempty() as the target string: + * format. When this is the need, just use sdsempty() as the target string: * - * s = hi_sdscatprintf(hi_sdsempty(), "... your format ...", args); + * s = sdscatprintf(sdsempty(), "... your format ...", args); */ -hisds hi_sdscatprintf(hisds s, const char *fmt, ...) { +sds sdscatprintf(sds s, const char *fmt, ...) { va_list ap; char *t; va_start(ap, fmt); - t = hi_sdscatvprintf(s,fmt,ap); + t = sdscatvprintf(s,fmt,ap); va_end(ap); return t; } -/* This function is similar to hi_sdscatprintf, but much faster as it does +/* This function is similar to sdscatprintf, but much faster as it does * not rely on sprintf() family functions implemented by the libc that - * are often very slow. Moreover directly handling the hisds string as + * are often very slow. Moreover directly handling the sds string as * new data is concatenated provides a performance improvement. * * However this function only handles an incompatible subset of printf-alike @@ -578,13 +578,13 @@ hisds hi_sdscatprintf(hisds s, const char *fmt, ...) { * %U - 64 bit unsigned integer (unsigned long long, uint64_t) * %% - Verbatim "%" character. */ -hisds hi_sdscatfmt(hisds s, char const *fmt, ...) { +sds sdscatfmt(sds s, char const *fmt, ...) { const char *f = fmt; int i; va_list ap; va_start(ap,fmt); - i = hi_sdslen(s); /* Position of the next byte to write to dest str. */ + i = sdslen(s); /* Position of the next byte to write to dest str. */ while(*f) { char next, *str; size_t l; @@ -592,8 +592,8 @@ hisds hi_sdscatfmt(hisds s, char const *fmt, ...) { unsigned long long unum; /* Make sure there is always space for at least 1 char. */ - if (hi_sdsavail(s)==0) { - s = hi_sdsMakeRoomFor(s,1); + if (sdsavail(s)==0) { + s = sdsMakeRoomFor(s,1); if (s == NULL) goto fmt_error; } @@ -605,13 +605,13 @@ hisds hi_sdscatfmt(hisds s, char const *fmt, ...) { case 's': case 'S': str = va_arg(ap,char*); - l = (next == 's') ? strlen(str) : hi_sdslen(str); - if (hi_sdsavail(s) < l) { - s = hi_sdsMakeRoomFor(s,l); + l = (next == 's') ? strlen(str) : sdslen(str); + if (sdsavail(s) < l) { + s = sdsMakeRoomFor(s,l); if (s == NULL) goto fmt_error; } memcpy(s+i,str,l); - hi_sdsinclen(s,l); + sdsinclen(s,l); i += l; break; case 'i': @@ -621,14 +621,14 @@ hisds hi_sdscatfmt(hisds s, char const *fmt, ...) { else num = va_arg(ap,long long); { - char buf[HI_SDS_LLSTR_SIZE]; - l = hi_sdsll2str(buf,num); - if (hi_sdsavail(s) < l) { - s = hi_sdsMakeRoomFor(s,l); + char buf[SDS_LLSTR_SIZE]; + l = sdsll2str(buf,num); + if (sdsavail(s) < l) { + s = sdsMakeRoomFor(s,l); if (s == NULL) goto fmt_error; } memcpy(s+i,buf,l); - hi_sdsinclen(s,l); + sdsinclen(s,l); i += l; } break; @@ -639,26 +639,26 @@ hisds hi_sdscatfmt(hisds s, char const *fmt, ...) { else unum = va_arg(ap,unsigned long long); { - char buf[HI_SDS_LLSTR_SIZE]; - l = hi_sdsull2str(buf,unum); - if (hi_sdsavail(s) < l) { - s = hi_sdsMakeRoomFor(s,l); + char buf[SDS_LLSTR_SIZE]; + l = sdsull2str(buf,unum); + if (sdsavail(s) < l) { + s = sdsMakeRoomFor(s,l); if (s == NULL) goto fmt_error; } memcpy(s+i,buf,l); - hi_sdsinclen(s,l); + sdsinclen(s,l); i += l; } break; default: /* Handle %% and generally %. */ s[i++] = next; - hi_sdsinclen(s,1); + sdsinclen(s,1); break; } break; default: s[i++] = *f; - hi_sdsinclen(s,1); + sdsinclen(s,1); break; } f++; @@ -677,29 +677,29 @@ fmt_error: /* Remove the part of the string from left and from right composed just of * contiguous characters found in 'cset', that is a null terminted C string. * - * After the call, the modified hisds string is no longer valid and all the + * After the call, the modified sds string is no longer valid and all the * references must be substituted with the new pointer returned by the call. * * Example: * - * s = hi_sdsnew("AA...AA.a.aa.aHelloWorld :::"); - * s = hi_sdstrim(s,"Aa. :"); + * s = sdsnew("AA...AA.a.aa.aHelloWorld :::"); + * s = sdstrim(s,"Aa. :"); * printf("%s\n", s); * * Output will be just "Hello World". */ -hisds hi_sdstrim(hisds s, const char *cset) { +sds sdstrim(sds s, const char *cset) { char *start, *end, *sp, *ep; size_t len; sp = start = s; - ep = end = s+hi_sdslen(s)-1; + ep = end = s+sdslen(s)-1; while(sp <= end && strchr(cset, *sp)) sp++; while(ep > sp && strchr(cset, *ep)) ep--; len = (sp > ep) ? 0 : ((ep-sp)+1); if (s != sp) memmove(s, sp, len); s[len] = '\0'; - hi_sdssetlen(s,len); + sdssetlen(s,len); return s; } @@ -715,16 +715,16 @@ hisds hi_sdstrim(hisds s, const char *cset) { * The string is modified in-place. * * Return value: - * -1 (error) if hi_sdslen(s) is larger than maximum positive ssize_t value. + * -1 (error) if sdslen(s) is larger than maximum positive ssize_t value. * 0 on success. * * Example: * - * s = hi_sdsnew("Hello World"); - * hi_sdsrange(s,1,-1); => "ello World" + * s = sdsnew("Hello World"); + * sdsrange(s,1,-1); => "ello World" */ -int hi_sdsrange(hisds s, ssize_t start, ssize_t end) { - size_t newlen, len = hi_sdslen(s); +int sdsrange(sds s, ssize_t start, ssize_t end) { + size_t newlen, len = sdslen(s); if (len > SSIZE_MAX) return -1; if (len == 0) return 0; @@ -749,25 +749,25 @@ int hi_sdsrange(hisds s, ssize_t start, ssize_t end) { } if (start && newlen) memmove(s, s+start, newlen); s[newlen] = 0; - hi_sdssetlen(s,newlen); + sdssetlen(s,newlen); return 0; } -/* Apply tolower() to every character of the hisds string 's'. */ -void hi_sdstolower(hisds s) { - int len = hi_sdslen(s), j; +/* Apply tolower() to every character of the sds string 's'. */ +void sdstolower(sds s) { + int len = sdslen(s), j; for (j = 0; j < len; j++) s[j] = tolower(s[j]); } -/* Apply toupper() to every character of the hisds string 's'. */ -void hi_sdstoupper(hisds s) { - int len = hi_sdslen(s), j; +/* Apply toupper() to every character of the sds string 's'. */ +void sdstoupper(sds s) { + int len = sdslen(s), j; for (j = 0; j < len; j++) s[j] = toupper(s[j]); } -/* Compare two hisds strings s1 and s2 with memcmp(). +/* Compare two sds strings s1 and s2 with memcmp(). * * Return value: * @@ -778,12 +778,12 @@ void hi_sdstoupper(hisds s) { * If two strings share exactly the same prefix, but one of the two has * additional characters, the longer string is considered to be greater than * the smaller one. */ -int hi_sdscmp(const hisds s1, const hisds s2) { +int sdscmp(const sds s1, const sds s2) { size_t l1, l2, minlen; int cmp; - l1 = hi_sdslen(s1); - l2 = hi_sdslen(s2); + l1 = sdslen(s1); + l2 = sdslen(s2); minlen = (l1 < l2) ? l1 : l2; cmp = memcmp(s1,s2,minlen); if (cmp == 0) return l1-l2; @@ -791,7 +791,7 @@ int hi_sdscmp(const hisds s1, const hisds s2) { } /* Split 's' with separator in 'sep'. An array - * of hisds strings is returned. *count will be set + * of sds strings is returned. *count will be set * by reference to the number of tokens returned. * * On out of memory, zero length string, zero length @@ -799,20 +799,20 @@ int hi_sdscmp(const hisds s1, const hisds s2) { * * Note that 'sep' is able to split a string using * a multi-character separator. For example - * hi_sdssplit("foo_-_bar","_-_"); will return two + * sdssplit("foo_-_bar","_-_"); will return two * elements "foo" and "bar". * * This version of the function is binary-safe but - * requires length arguments. hi_sdssplit() is just the + * requires length arguments. sdssplit() is just the * same function but for zero-terminated strings. */ -hisds *hi_sdssplitlen(const char *s, int len, const char *sep, int seplen, int *count) { +sds *sdssplitlen(const char *s, int len, const char *sep, int seplen, int *count) { int elements = 0, slots = 5, start = 0, j; - hisds *tokens; + sds *tokens; if (seplen < 1 || len < 0) return NULL; - tokens = hi_s_malloc(sizeof(hisds)*slots); + tokens = s_malloc(sizeof(sds)*slots); if (tokens == NULL) return NULL; if (len == 0) { @@ -822,16 +822,16 @@ hisds *hi_sdssplitlen(const char *s, int len, const char *sep, int seplen, int * for (j = 0; j < (len-(seplen-1)); j++) { /* make sure there is room for the next element and the final one */ if (slots < elements+2) { - hisds *newtokens; + sds *newtokens; slots *= 2; - newtokens = hi_s_realloc(tokens,sizeof(hisds)*slots); + newtokens = s_realloc(tokens,sizeof(sds)*slots); if (newtokens == NULL) goto cleanup; tokens = newtokens; } /* search the separator */ if ((seplen == 1 && *(s+j) == sep[0]) || (memcmp(s+j,sep,seplen) == 0)) { - tokens[elements] = hi_sdsnewlen(s+start,j-start); + tokens[elements] = sdsnewlen(s+start,j-start); if (tokens[elements] == NULL) goto cleanup; elements++; start = j+seplen; @@ -839,7 +839,7 @@ hisds *hi_sdssplitlen(const char *s, int len, const char *sep, int seplen, int * } } /* Add the final element. We are sure there is room in the tokens array. */ - tokens[elements] = hi_sdsnewlen(s+start,len-start); + tokens[elements] = sdsnewlen(s+start,len-start); if (tokens[elements] == NULL) goto cleanup; elements++; *count = elements; @@ -848,55 +848,55 @@ hisds *hi_sdssplitlen(const char *s, int len, const char *sep, int seplen, int * cleanup: { int i; - for (i = 0; i < elements; i++) hi_sdsfree(tokens[i]); - hi_s_free(tokens); + for (i = 0; i < elements; i++) sdsfree(tokens[i]); + s_free(tokens); *count = 0; return NULL; } } -/* Free the result returned by hi_sdssplitlen(), or do nothing if 'tokens' is NULL. */ -void hi_sdsfreesplitres(hisds *tokens, int count) { +/* Free the result returned by sdssplitlen(), or do nothing if 'tokens' is NULL. */ +void sdsfreesplitres(sds *tokens, int count) { if (!tokens) return; while(count--) - hi_sdsfree(tokens[count]); - hi_s_free(tokens); + sdsfree(tokens[count]); + s_free(tokens); } -/* Append to the hisds string "s" an escaped string representation where +/* Append to the sds string "s" an escaped string representation where * all the non-printable characters (tested with isprint()) are turned into * escapes in the form "\n\r\a...." or "\x". * - * After the call, the modified hisds string is no longer valid and all the + * After the call, the modified sds string is no longer valid and all the * references must be substituted with the new pointer returned by the call. */ -hisds hi_sdscatrepr(hisds s, const char *p, size_t len) { - s = hi_sdscatlen(s,"\"",1); +sds sdscatrepr(sds s, const char *p, size_t len) { + s = sdscatlen(s,"\"",1); while(len--) { switch(*p) { case '\\': case '"': - s = hi_sdscatprintf(s,"\\%c",*p); + s = sdscatprintf(s,"\\%c",*p); break; - case '\n': s = hi_sdscatlen(s,"\\n",2); break; - case '\r': s = hi_sdscatlen(s,"\\r",2); break; - case '\t': s = hi_sdscatlen(s,"\\t",2); break; - case '\a': s = hi_sdscatlen(s,"\\a",2); break; - case '\b': s = hi_sdscatlen(s,"\\b",2); break; + case '\n': s = sdscatlen(s,"\\n",2); break; + case '\r': s = sdscatlen(s,"\\r",2); break; + case '\t': s = sdscatlen(s,"\\t",2); break; + case '\a': s = sdscatlen(s,"\\a",2); break; + case '\b': s = sdscatlen(s,"\\b",2); break; default: if (isprint(*p)) - s = hi_sdscatprintf(s,"%c",*p); + s = sdscatprintf(s,"%c",*p); else - s = hi_sdscatprintf(s,"\\x%02x",(unsigned char)*p); + s = sdscatprintf(s,"\\x%02x",(unsigned char)*p); break; } p++; } - return hi_sdscatlen(s,"\"",1); + return sdscatlen(s,"\"",1); } -/* Helper function for hi_sdssplitargs() that converts a hex digit into an +/* Helper function for sdssplitargs() that converts a hex digit into an * integer from 0 to 15 */ -static int hi_hex_digit_to_int(char c) { +int hex_digit_to_int(char c) { switch(c) { case '0': return 0; case '1': return 1; @@ -924,20 +924,20 @@ static int hi_hex_digit_to_int(char c) { * foo bar "newline are supported\n" and "\xff\x00otherstuff" * * The number of arguments is stored into *argc, and an array - * of hisds is returned. + * of sds is returned. * - * The caller should free the resulting array of hisds strings with - * hi_sdsfreesplitres(). + * The caller should free the resulting array of sds strings with + * sdsfreesplitres(). * - * Note that hi_sdscatrepr() is able to convert back a string into - * a quoted string in the same format hi_sdssplitargs() is able to parse. + * Note that sdscatrepr() is able to convert back a string into + * a quoted string in the same format sdssplitargs() is able to parse. * * The function returns the allocated tokens on success, even when the * input string is empty, or NULL if the input contains unbalanced * quotes or closed quotes followed by non space characters * as in: "foo"bar or "foo' */ -hisds *hi_sdssplitargs(const char *line, int *argc) { +sds *sdssplitargs(const char *line, int *argc) { const char *p = line; char *current = NULL; char **vector = NULL; @@ -952,7 +952,7 @@ hisds *hi_sdssplitargs(const char *line, int *argc) { int insq=0; /* set to 1 if we are in 'single quotes' */ int done=0; - if (current == NULL) current = hi_sdsempty(); + if (current == NULL) current = sdsempty(); while(!done) { if (inq) { if (*p == '\\' && *(p+1) == 'x' && @@ -961,9 +961,9 @@ hisds *hi_sdssplitargs(const char *line, int *argc) { { unsigned char byte; - byte = (hi_hex_digit_to_int(*(p+2))*16)+ - hi_hex_digit_to_int(*(p+3)); - current = hi_sdscatlen(current,(char*)&byte,1); + byte = (hex_digit_to_int(*(p+2))*16)+ + hex_digit_to_int(*(p+3)); + current = sdscatlen(current,(char*)&byte,1); p += 3; } else if (*p == '\\' && *(p+1)) { char c; @@ -977,7 +977,7 @@ hisds *hi_sdssplitargs(const char *line, int *argc) { case 'a': c = '\a'; break; default: c = *p; break; } - current = hi_sdscatlen(current,&c,1); + current = sdscatlen(current,&c,1); } else if (*p == '"') { /* closing quote must be followed by a space or * nothing at all. */ @@ -987,12 +987,12 @@ hisds *hi_sdssplitargs(const char *line, int *argc) { /* unterminated quotes */ goto err; } else { - current = hi_sdscatlen(current,p,1); + current = sdscatlen(current,p,1); } } else if (insq) { if (*p == '\\' && *(p+1) == '\'') { p++; - current = hi_sdscatlen(current,"'",1); + current = sdscatlen(current,"'",1); } else if (*p == '\'') { /* closing quote must be followed by a space or * nothing at all. */ @@ -1002,7 +1002,7 @@ hisds *hi_sdssplitargs(const char *line, int *argc) { /* unterminated quotes */ goto err; } else { - current = hi_sdscatlen(current,p,1); + current = sdscatlen(current,p,1); } } else { switch(*p) { @@ -1020,7 +1020,7 @@ hisds *hi_sdssplitargs(const char *line, int *argc) { insq=1; break; default: - current = hi_sdscatlen(current,p,1); + current = sdscatlen(current,p,1); break; } } @@ -1028,9 +1028,9 @@ hisds *hi_sdssplitargs(const char *line, int *argc) { } /* add the token to the vector */ { - char **new_vector = hi_s_realloc(vector,((*argc)+1)*sizeof(char*)); + char **new_vector = s_realloc(vector,((*argc)+1)*sizeof(char*)); if (new_vector == NULL) { - hi_s_free(vector); + s_free(vector); return NULL; } @@ -1041,16 +1041,16 @@ hisds *hi_sdssplitargs(const char *line, int *argc) { } } else { /* Even on empty input string return something not NULL. */ - if (vector == NULL) vector = hi_s_malloc(sizeof(void*)); + if (vector == NULL) vector = s_malloc(sizeof(void*)); return vector; } } err: while((*argc)--) - hi_sdsfree(vector[*argc]); - hi_s_free(vector); - if (current) hi_sdsfree(current); + sdsfree(vector[*argc]); + s_free(vector); + if (current) sdsfree(current); *argc = 0; return NULL; } @@ -1059,13 +1059,13 @@ err: * characters specified in the 'from' string to the corresponding character * in the 'to' array. * - * For instance: hi_sdsmapchars(mystring, "ho", "01", 2) + * For instance: sdsmapchars(mystring, "ho", "01", 2) * will have the effect of turning the string "hello" into "0ell1". * - * The function returns the hisds string pointer, that is always the same + * The function returns the sds string pointer, that is always the same * as the input pointer since no resize is needed. */ -hisds hi_sdsmapchars(hisds s, const char *from, const char *to, size_t setlen) { - size_t j, i, l = hi_sdslen(s); +sds sdsmapchars(sds s, const char *from, const char *to, size_t setlen) { + size_t j, i, l = sdslen(s); for (j = 0; j < l; j++) { for (i = 0; i < setlen; i++) { @@ -1079,26 +1079,26 @@ hisds hi_sdsmapchars(hisds s, const char *from, const char *to, size_t setlen) { } /* Join an array of C strings using the specified separator (also a C string). - * Returns the result as an hisds string. */ -hisds hi_sdsjoin(char **argv, int argc, char *sep) { - hisds join = hi_sdsempty(); + * Returns the result as an sds string. */ +sds sdsjoin(char **argv, int argc, char *sep) { + sds join = sdsempty(); int j; for (j = 0; j < argc; j++) { - join = hi_sdscat(join, argv[j]); - if (j != argc-1) join = hi_sdscat(join,sep); + join = sdscat(join, argv[j]); + if (j != argc-1) join = sdscat(join,sep); } return join; } -/* Like hi_sdsjoin, but joins an array of SDS strings. */ -hisds hi_sdsjoinsds(hisds *argv, int argc, const char *sep, size_t seplen) { - hisds join = hi_sdsempty(); +/* Like sdsjoin, but joins an array of SDS strings. */ +sds sdsjoinsds(sds *argv, int argc, const char *sep, size_t seplen) { + sds join = sdsempty(); int j; for (j = 0; j < argc; j++) { - join = hi_sdscatsds(join, argv[j]); - if (j != argc-1) join = hi_sdscatlen(join,sep,seplen); + join = sdscatsds(join, argv[j]); + if (j != argc-1) join = sdscatlen(join,sep,seplen); } return join; } @@ -1108,138 +1108,138 @@ hisds hi_sdsjoinsds(hisds *argv, int argc, const char *sep, size_t seplen) { * the overhead of function calls. Here we define these wrappers only for * the programs SDS is linked to, if they want to touch the SDS internals * even if they use a different allocator. */ -void *hi_sds_malloc(size_t size) { return hi_s_malloc(size); } -void *hi_sds_realloc(void *ptr, size_t size) { return hi_s_realloc(ptr,size); } -void hi_sds_free(void *ptr) { hi_s_free(ptr); } +void *sds_malloc(size_t size) { return s_malloc(size); } +void *sds_realloc(void *ptr, size_t size) { return s_realloc(ptr,size); } +void sds_free(void *ptr) { s_free(ptr); } -#if defined(HI_SDS_TEST_MAIN) +#if defined(SDS_TEST_MAIN) #include #include "testhelp.h" #include "limits.h" #define UNUSED(x) (void)(x) -int hi_sdsTest(void) { +int sdsTest(void) { { - hisds x = hi_sdsnew("foo"), y; + sds x = sdsnew("foo"), y; test_cond("Create a string and obtain the length", - hi_sdslen(x) == 3 && memcmp(x,"foo\0",4) == 0) + sdslen(x) == 3 && memcmp(x,"foo\0",4) == 0) - hi_sdsfree(x); - x = hi_sdsnewlen("foo",2); + sdsfree(x); + x = sdsnewlen("foo",2); test_cond("Create a string with specified length", - hi_sdslen(x) == 2 && memcmp(x,"fo\0",3) == 0) + sdslen(x) == 2 && memcmp(x,"fo\0",3) == 0) - x = hi_sdscat(x,"bar"); + x = sdscat(x,"bar"); test_cond("Strings concatenation", - hi_sdslen(x) == 5 && memcmp(x,"fobar\0",6) == 0); + sdslen(x) == 5 && memcmp(x,"fobar\0",6) == 0); - x = hi_sdscpy(x,"a"); - test_cond("hi_sdscpy() against an originally longer string", - hi_sdslen(x) == 1 && memcmp(x,"a\0",2) == 0) + x = sdscpy(x,"a"); + test_cond("sdscpy() against an originally longer string", + sdslen(x) == 1 && memcmp(x,"a\0",2) == 0) - x = hi_sdscpy(x,"xyzxxxxxxxxxxyyyyyyyyyykkkkkkkkkk"); - test_cond("hi_sdscpy() against an originally shorter string", - hi_sdslen(x) == 33 && + x = sdscpy(x,"xyzxxxxxxxxxxyyyyyyyyyykkkkkkkkkk"); + test_cond("sdscpy() against an originally shorter string", + sdslen(x) == 33 && memcmp(x,"xyzxxxxxxxxxxyyyyyyyyyykkkkkkkkkk\0",33) == 0) - hi_sdsfree(x); - x = hi_sdscatprintf(hi_sdsempty(),"%d",123); - test_cond("hi_sdscatprintf() seems working in the base case", - hi_sdslen(x) == 3 && memcmp(x,"123\0",4) == 0) + sdsfree(x); + x = sdscatprintf(sdsempty(),"%d",123); + test_cond("sdscatprintf() seems working in the base case", + sdslen(x) == 3 && memcmp(x,"123\0",4) == 0) - hi_sdsfree(x); - x = hi_sdsnew("--"); - x = hi_sdscatfmt(x, "Hello %s World %I,%I--", "Hi!", LLONG_MIN,LLONG_MAX); - test_cond("hi_sdscatfmt() seems working in the base case", - hi_sdslen(x) == 60 && + sdsfree(x); + x = sdsnew("--"); + x = sdscatfmt(x, "Hello %s World %I,%I--", "Hi!", LLONG_MIN,LLONG_MAX); + test_cond("sdscatfmt() seems working in the base case", + sdslen(x) == 60 && memcmp(x,"--Hello Hi! World -9223372036854775808," "9223372036854775807--",60) == 0) printf("[%s]\n",x); - hi_sdsfree(x); - x = hi_sdsnew("--"); - x = hi_sdscatfmt(x, "%u,%U--", UINT_MAX, ULLONG_MAX); - test_cond("hi_sdscatfmt() seems working with unsigned numbers", - hi_sdslen(x) == 35 && + sdsfree(x); + x = sdsnew("--"); + x = sdscatfmt(x, "%u,%U--", UINT_MAX, ULLONG_MAX); + test_cond("sdscatfmt() seems working with unsigned numbers", + sdslen(x) == 35 && memcmp(x,"--4294967295,18446744073709551615--",35) == 0) - hi_sdsfree(x); - x = hi_sdsnew(" x "); - hi_sdstrim(x," x"); - test_cond("hi_sdstrim() works when all chars match", - hi_sdslen(x) == 0) + sdsfree(x); + x = sdsnew(" x "); + sdstrim(x," x"); + test_cond("sdstrim() works when all chars match", + sdslen(x) == 0) - hi_sdsfree(x); - x = hi_sdsnew(" x "); - hi_sdstrim(x," "); - test_cond("hi_sdstrim() works when a single char remains", - hi_sdslen(x) == 1 && x[0] == 'x') + sdsfree(x); + x = sdsnew(" x "); + sdstrim(x," "); + test_cond("sdstrim() works when a single char remains", + sdslen(x) == 1 && x[0] == 'x') - hi_sdsfree(x); - x = hi_sdsnew("xxciaoyyy"); - hi_sdstrim(x,"xy"); - test_cond("hi_sdstrim() correctly trims characters", - hi_sdslen(x) == 4 && memcmp(x,"ciao\0",5) == 0) + sdsfree(x); + x = sdsnew("xxciaoyyy"); + sdstrim(x,"xy"); + test_cond("sdstrim() correctly trims characters", + sdslen(x) == 4 && memcmp(x,"ciao\0",5) == 0) - y = hi_sdsdup(x); - hi_sdsrange(y,1,1); - test_cond("hi_sdsrange(...,1,1)", - hi_sdslen(y) == 1 && memcmp(y,"i\0",2) == 0) + y = sdsdup(x); + sdsrange(y,1,1); + test_cond("sdsrange(...,1,1)", + sdslen(y) == 1 && memcmp(y,"i\0",2) == 0) - hi_sdsfree(y); - y = hi_sdsdup(x); - hi_sdsrange(y,1,-1); - test_cond("hi_sdsrange(...,1,-1)", - hi_sdslen(y) == 3 && memcmp(y,"iao\0",4) == 0) + sdsfree(y); + y = sdsdup(x); + sdsrange(y,1,-1); + test_cond("sdsrange(...,1,-1)", + sdslen(y) == 3 && memcmp(y,"iao\0",4) == 0) - hi_sdsfree(y); - y = hi_sdsdup(x); - hi_sdsrange(y,-2,-1); - test_cond("hi_sdsrange(...,-2,-1)", - hi_sdslen(y) == 2 && memcmp(y,"ao\0",3) == 0) + sdsfree(y); + y = sdsdup(x); + sdsrange(y,-2,-1); + test_cond("sdsrange(...,-2,-1)", + sdslen(y) == 2 && memcmp(y,"ao\0",3) == 0) - hi_sdsfree(y); - y = hi_sdsdup(x); - hi_sdsrange(y,2,1); - test_cond("hi_sdsrange(...,2,1)", - hi_sdslen(y) == 0 && memcmp(y,"\0",1) == 0) + sdsfree(y); + y = sdsdup(x); + sdsrange(y,2,1); + test_cond("sdsrange(...,2,1)", + sdslen(y) == 0 && memcmp(y,"\0",1) == 0) - hi_sdsfree(y); - y = hi_sdsdup(x); - hi_sdsrange(y,1,100); - test_cond("hi_sdsrange(...,1,100)", - hi_sdslen(y) == 3 && memcmp(y,"iao\0",4) == 0) + sdsfree(y); + y = sdsdup(x); + sdsrange(y,1,100); + test_cond("sdsrange(...,1,100)", + sdslen(y) == 3 && memcmp(y,"iao\0",4) == 0) - hi_sdsfree(y); - y = hi_sdsdup(x); - hi_sdsrange(y,100,100); - test_cond("hi_sdsrange(...,100,100)", - hi_sdslen(y) == 0 && memcmp(y,"\0",1) == 0) + sdsfree(y); + y = sdsdup(x); + sdsrange(y,100,100); + test_cond("sdsrange(...,100,100)", + sdslen(y) == 0 && memcmp(y,"\0",1) == 0) - hi_sdsfree(y); - hi_sdsfree(x); - x = hi_sdsnew("foo"); - y = hi_sdsnew("foa"); - test_cond("hi_sdscmp(foo,foa)", hi_sdscmp(x,y) > 0) + sdsfree(y); + sdsfree(x); + x = sdsnew("foo"); + y = sdsnew("foa"); + test_cond("sdscmp(foo,foa)", sdscmp(x,y) > 0) - hi_sdsfree(y); - hi_sdsfree(x); - x = hi_sdsnew("bar"); - y = hi_sdsnew("bar"); - test_cond("hi_sdscmp(bar,bar)", hi_sdscmp(x,y) == 0) + sdsfree(y); + sdsfree(x); + x = sdsnew("bar"); + y = sdsnew("bar"); + test_cond("sdscmp(bar,bar)", sdscmp(x,y) == 0) - hi_sdsfree(y); - hi_sdsfree(x); - x = hi_sdsnew("aar"); - y = hi_sdsnew("bar"); - test_cond("hi_sdscmp(bar,bar)", hi_sdscmp(x,y) < 0) + sdsfree(y); + sdsfree(x); + x = sdsnew("aar"); + y = sdsnew("bar"); + test_cond("sdscmp(bar,bar)", sdscmp(x,y) < 0) - hi_sdsfree(y); - hi_sdsfree(x); - x = hi_sdsnewlen("\a\n\0foo\r",7); - y = hi_sdscatrepr(hi_sdsempty(),x,hi_sdslen(x)); - test_cond("hi_sdscatrepr(...data...)", + sdsfree(y); + sdsfree(x); + x = sdsnewlen("\a\n\0foo\r",7); + y = sdscatrepr(sdsempty(),x,sdslen(x)); + test_cond("sdscatrepr(...data...)", memcmp(y,"\"\\a\\n\\x00foo\\r\"",15) == 0) { @@ -1247,43 +1247,43 @@ int hi_sdsTest(void) { char *p; int step = 10, j, i; - hi_sdsfree(x); - hi_sdsfree(y); - x = hi_sdsnew("0"); - test_cond("hi_sdsnew() free/len buffers", hi_sdslen(x) == 1 && hi_sdsavail(x) == 0); + sdsfree(x); + sdsfree(y); + x = sdsnew("0"); + test_cond("sdsnew() free/len buffers", sdslen(x) == 1 && sdsavail(x) == 0); /* Run the test a few times in order to hit the first two * SDS header types. */ for (i = 0; i < 10; i++) { - int oldlen = hi_sdslen(x); - x = hi_sdsMakeRoomFor(x,step); - int type = x[-1]&HI_SDS_TYPE_MASK; + int oldlen = sdslen(x); + x = sdsMakeRoomFor(x,step); + int type = x[-1]&SDS_TYPE_MASK; - test_cond("sdsMakeRoomFor() len", hi_sdslen(x) == oldlen); - if (type != HI_SDS_TYPE_5) { - test_cond("hi_sdsMakeRoomFor() free", hi_sdsavail(x) >= step); - oldfree = hi_sdsavail(x); + test_cond("sdsMakeRoomFor() len", sdslen(x) == oldlen); + if (type != SDS_TYPE_5) { + test_cond("sdsMakeRoomFor() free", sdsavail(x) >= step); + oldfree = sdsavail(x); } p = x+oldlen; for (j = 0; j < step; j++) { p[j] = 'A'+j; } - hi_sdsIncrLen(x,step); + sdsIncrLen(x,step); } - test_cond("hi_sdsMakeRoomFor() content", + test_cond("sdsMakeRoomFor() content", memcmp("0ABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJ",x,101) == 0); - test_cond("sdsMakeRoomFor() final length",hi_sdslen(x)==101); + test_cond("sdsMakeRoomFor() final length",sdslen(x)==101); - hi_sdsfree(x); + sdsfree(x); } } - test_report(); + test_report() return 0; } #endif -#ifdef HI_SDS_TEST_MAIN +#ifdef SDS_TEST_MAIN int main(void) { - return hi_sdsTest(); + return sdsTest(); } #endif diff --git a/sds.h b/sds.h index 573d6dd19..eda8833b5 100644 --- a/sds.h +++ b/sds.h @@ -30,10 +30,10 @@ * POSSIBILITY OF SUCH DAMAGE. */ -#ifndef HIREDIS_SDS_H -#define HIREDIS_SDS_H +#ifndef __SDS_H +#define __SDS_H -#define HI_SDS_MAX_PREALLOC (1024*1024) +#define SDS_MAX_PREALLOC (1024*1024) #ifdef _MSC_VER #define __attribute__(x) typedef long long ssize_t; @@ -44,235 +44,235 @@ typedef long long ssize_t; #include #include -typedef char *hisds; +typedef char *sds; /* Note: sdshdr5 is never used, we just access the flags byte directly. * However is here to document the layout of type 5 SDS strings. */ -struct __attribute__ ((__packed__)) hisdshdr5 { +struct __attribute__ ((__packed__)) sdshdr5 { unsigned char flags; /* 3 lsb of type, and 5 msb of string length */ char buf[]; }; -struct __attribute__ ((__packed__)) hisdshdr8 { +struct __attribute__ ((__packed__)) sdshdr8 { uint8_t len; /* used */ uint8_t alloc; /* excluding the header and null terminator */ unsigned char flags; /* 3 lsb of type, 5 unused bits */ char buf[]; }; -struct __attribute__ ((__packed__)) hisdshdr16 { +struct __attribute__ ((__packed__)) sdshdr16 { uint16_t len; /* used */ uint16_t alloc; /* excluding the header and null terminator */ unsigned char flags; /* 3 lsb of type, 5 unused bits */ char buf[]; }; -struct __attribute__ ((__packed__)) hisdshdr32 { +struct __attribute__ ((__packed__)) sdshdr32 { uint32_t len; /* used */ uint32_t alloc; /* excluding the header and null terminator */ unsigned char flags; /* 3 lsb of type, 5 unused bits */ char buf[]; }; -struct __attribute__ ((__packed__)) hisdshdr64 { +struct __attribute__ ((__packed__)) sdshdr64 { uint64_t len; /* used */ uint64_t alloc; /* excluding the header and null terminator */ unsigned char flags; /* 3 lsb of type, 5 unused bits */ char buf[]; }; -#define HI_SDS_TYPE_5 0 -#define HI_SDS_TYPE_8 1 -#define HI_SDS_TYPE_16 2 -#define HI_SDS_TYPE_32 3 -#define HI_SDS_TYPE_64 4 -#define HI_SDS_TYPE_MASK 7 -#define HI_SDS_TYPE_BITS 3 -#define HI_SDS_HDR_VAR(T,s) struct hisdshdr##T *sh = (struct hisdshdr##T *)((s)-(sizeof(struct hisdshdr##T))); -#define HI_SDS_HDR(T,s) ((struct hisdshdr##T *)((s)-(sizeof(struct hisdshdr##T)))) -#define HI_SDS_TYPE_5_LEN(f) ((f)>>HI_SDS_TYPE_BITS) +#define SDS_TYPE_5 0 +#define SDS_TYPE_8 1 +#define SDS_TYPE_16 2 +#define SDS_TYPE_32 3 +#define SDS_TYPE_64 4 +#define SDS_TYPE_MASK 7 +#define SDS_TYPE_BITS 3 +#define SDS_HDR_VAR(T,s) struct sdshdr##T *sh = (struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T))); +#define SDS_HDR(T,s) ((struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T)))) +#define SDS_TYPE_5_LEN(f) ((f)>>SDS_TYPE_BITS) -static inline size_t hi_sdslen(const hisds s) { +static inline size_t sdslen(const sds s) { unsigned char flags = s[-1]; - switch(flags & HI_SDS_TYPE_MASK) { - case HI_SDS_TYPE_5: - return HI_SDS_TYPE_5_LEN(flags); - case HI_SDS_TYPE_8: - return HI_SDS_HDR(8,s)->len; - case HI_SDS_TYPE_16: - return HI_SDS_HDR(16,s)->len; - case HI_SDS_TYPE_32: - return HI_SDS_HDR(32,s)->len; - case HI_SDS_TYPE_64: - return HI_SDS_HDR(64,s)->len; + switch(flags&SDS_TYPE_MASK) { + case SDS_TYPE_5: + return SDS_TYPE_5_LEN(flags); + case SDS_TYPE_8: + return SDS_HDR(8,s)->len; + case SDS_TYPE_16: + return SDS_HDR(16,s)->len; + case SDS_TYPE_32: + return SDS_HDR(32,s)->len; + case SDS_TYPE_64: + return SDS_HDR(64,s)->len; } return 0; } -static inline size_t hi_sdsavail(const hisds s) { +static inline size_t sdsavail(const sds s) { unsigned char flags = s[-1]; - switch(flags&HI_SDS_TYPE_MASK) { - case HI_SDS_TYPE_5: { + switch(flags&SDS_TYPE_MASK) { + case SDS_TYPE_5: { return 0; } - case HI_SDS_TYPE_8: { - HI_SDS_HDR_VAR(8,s); + case SDS_TYPE_8: { + SDS_HDR_VAR(8,s); return sh->alloc - sh->len; } - case HI_SDS_TYPE_16: { - HI_SDS_HDR_VAR(16,s); + case SDS_TYPE_16: { + SDS_HDR_VAR(16,s); return sh->alloc - sh->len; } - case HI_SDS_TYPE_32: { - HI_SDS_HDR_VAR(32,s); + case SDS_TYPE_32: { + SDS_HDR_VAR(32,s); return sh->alloc - sh->len; } - case HI_SDS_TYPE_64: { - HI_SDS_HDR_VAR(64,s); + case SDS_TYPE_64: { + SDS_HDR_VAR(64,s); return sh->alloc - sh->len; } } return 0; } -static inline void hi_sdssetlen(hisds s, size_t newlen) { +static inline void sdssetlen(sds s, size_t newlen) { unsigned char flags = s[-1]; - switch(flags&HI_SDS_TYPE_MASK) { - case HI_SDS_TYPE_5: + switch(flags&SDS_TYPE_MASK) { + case SDS_TYPE_5: { unsigned char *fp = ((unsigned char*)s)-1; - *fp = (unsigned char)(HI_SDS_TYPE_5 | (newlen << HI_SDS_TYPE_BITS)); + *fp = (unsigned char)(SDS_TYPE_5 | (newlen << SDS_TYPE_BITS)); } break; - case HI_SDS_TYPE_8: - HI_SDS_HDR(8,s)->len = (uint8_t)newlen; + case SDS_TYPE_8: + SDS_HDR(8,s)->len = (uint8_t)newlen; break; - case HI_SDS_TYPE_16: - HI_SDS_HDR(16,s)->len = (uint16_t)newlen; + case SDS_TYPE_16: + SDS_HDR(16,s)->len = (uint16_t)newlen; break; - case HI_SDS_TYPE_32: - HI_SDS_HDR(32,s)->len = (uint32_t)newlen; + case SDS_TYPE_32: + SDS_HDR(32,s)->len = (uint32_t)newlen; break; - case HI_SDS_TYPE_64: - HI_SDS_HDR(64,s)->len = (uint64_t)newlen; + case SDS_TYPE_64: + SDS_HDR(64,s)->len = (uint64_t)newlen; break; } } -static inline void hi_sdsinclen(hisds s, size_t inc) { +static inline void sdsinclen(sds s, size_t inc) { unsigned char flags = s[-1]; - switch(flags&HI_SDS_TYPE_MASK) { - case HI_SDS_TYPE_5: + switch(flags&SDS_TYPE_MASK) { + case SDS_TYPE_5: { unsigned char *fp = ((unsigned char*)s)-1; - unsigned char newlen = HI_SDS_TYPE_5_LEN(flags)+(unsigned char)inc; - *fp = HI_SDS_TYPE_5 | (newlen << HI_SDS_TYPE_BITS); + unsigned char newlen = SDS_TYPE_5_LEN(flags)+(unsigned char)inc; + *fp = SDS_TYPE_5 | (newlen << SDS_TYPE_BITS); } break; - case HI_SDS_TYPE_8: - HI_SDS_HDR(8,s)->len += (uint8_t)inc; + case SDS_TYPE_8: + SDS_HDR(8,s)->len += (uint8_t)inc; break; - case HI_SDS_TYPE_16: - HI_SDS_HDR(16,s)->len += (uint16_t)inc; + case SDS_TYPE_16: + SDS_HDR(16,s)->len += (uint16_t)inc; break; - case HI_SDS_TYPE_32: - HI_SDS_HDR(32,s)->len += (uint32_t)inc; + case SDS_TYPE_32: + SDS_HDR(32,s)->len += (uint32_t)inc; break; - case HI_SDS_TYPE_64: - HI_SDS_HDR(64,s)->len += (uint64_t)inc; + case SDS_TYPE_64: + SDS_HDR(64,s)->len += (uint64_t)inc; break; } } -/* hi_sdsalloc() = hi_sdsavail() + hi_sdslen() */ -static inline size_t hi_sdsalloc(const hisds s) { +/* sdsalloc() = sdsavail() + sdslen() */ +static inline size_t sdsalloc(const sds s) { unsigned char flags = s[-1]; - switch(flags & HI_SDS_TYPE_MASK) { - case HI_SDS_TYPE_5: - return HI_SDS_TYPE_5_LEN(flags); - case HI_SDS_TYPE_8: - return HI_SDS_HDR(8,s)->alloc; - case HI_SDS_TYPE_16: - return HI_SDS_HDR(16,s)->alloc; - case HI_SDS_TYPE_32: - return HI_SDS_HDR(32,s)->alloc; - case HI_SDS_TYPE_64: - return HI_SDS_HDR(64,s)->alloc; + switch(flags&SDS_TYPE_MASK) { + case SDS_TYPE_5: + return SDS_TYPE_5_LEN(flags); + case SDS_TYPE_8: + return SDS_HDR(8,s)->alloc; + case SDS_TYPE_16: + return SDS_HDR(16,s)->alloc; + case SDS_TYPE_32: + return SDS_HDR(32,s)->alloc; + case SDS_TYPE_64: + return SDS_HDR(64,s)->alloc; } return 0; } -static inline void hi_sdssetalloc(hisds s, size_t newlen) { +static inline void sdssetalloc(sds s, size_t newlen) { unsigned char flags = s[-1]; - switch(flags&HI_SDS_TYPE_MASK) { - case HI_SDS_TYPE_5: + switch(flags&SDS_TYPE_MASK) { + case SDS_TYPE_5: /* Nothing to do, this type has no total allocation info. */ break; - case HI_SDS_TYPE_8: - HI_SDS_HDR(8,s)->alloc = (uint8_t)newlen; + case SDS_TYPE_8: + SDS_HDR(8,s)->alloc = (uint8_t)newlen; break; - case HI_SDS_TYPE_16: - HI_SDS_HDR(16,s)->alloc = (uint16_t)newlen; + case SDS_TYPE_16: + SDS_HDR(16,s)->alloc = (uint16_t)newlen; break; - case HI_SDS_TYPE_32: - HI_SDS_HDR(32,s)->alloc = (uint32_t)newlen; + case SDS_TYPE_32: + SDS_HDR(32,s)->alloc = (uint32_t)newlen; break; - case HI_SDS_TYPE_64: - HI_SDS_HDR(64,s)->alloc = (uint64_t)newlen; + case SDS_TYPE_64: + SDS_HDR(64,s)->alloc = (uint64_t)newlen; break; } } -hisds hi_sdsnewlen(const void *init, size_t initlen); -hisds hi_sdsnew(const char *init); -hisds hi_sdsempty(void); -hisds hi_sdsdup(const hisds s); -void hi_sdsfree(hisds s); -hisds hi_sdsgrowzero(hisds s, size_t len); -hisds hi_sdscatlen(hisds s, const void *t, size_t len); -hisds hi_sdscat(hisds s, const char *t); -hisds hi_sdscatsds(hisds s, const hisds t); -hisds hi_sdscpylen(hisds s, const char *t, size_t len); -hisds hi_sdscpy(hisds s, const char *t); +sds sdsnewlen(const void *init, size_t initlen); +sds sdsnew(const char *init); +sds sdsempty(void); +sds sdsdup(const sds s); +void sdsfree(sds s); +sds sdsgrowzero(sds s, size_t len); +sds sdscatlen(sds s, const void *t, size_t len); +sds sdscat(sds s, const char *t); +sds sdscatsds(sds s, const sds t); +sds sdscpylen(sds s, const char *t, size_t len); +sds sdscpy(sds s, const char *t); -hisds hi_sdscatvprintf(hisds s, const char *fmt, va_list ap); +sds sdscatvprintf(sds s, const char *fmt, va_list ap); #ifdef __GNUC__ -hisds hi_sdscatprintf(hisds s, const char *fmt, ...) +sds sdscatprintf(sds s, const char *fmt, ...) __attribute__((format(printf, 2, 3))); #else -hisds hi_sdscatprintf(hisds s, const char *fmt, ...); +sds sdscatprintf(sds s, const char *fmt, ...); #endif -hisds hi_sdscatfmt(hisds s, char const *fmt, ...); -hisds hi_sdstrim(hisds s, const char *cset); -int hi_sdsrange(hisds s, ssize_t start, ssize_t end); -void hi_sdsupdatelen(hisds s); -void hi_sdsclear(hisds s); -int hi_sdscmp(const hisds s1, const hisds s2); -hisds *hi_sdssplitlen(const char *s, int len, const char *sep, int seplen, int *count); -void hi_sdsfreesplitres(hisds *tokens, int count); -void hi_sdstolower(hisds s); -void hi_sdstoupper(hisds s); -hisds hi_sdsfromlonglong(long long value); -hisds hi_sdscatrepr(hisds s, const char *p, size_t len); -hisds *hi_sdssplitargs(const char *line, int *argc); -hisds hi_sdsmapchars(hisds s, const char *from, const char *to, size_t setlen); -hisds hi_sdsjoin(char **argv, int argc, char *sep); -hisds hi_sdsjoinsds(hisds *argv, int argc, const char *sep, size_t seplen); +sds sdscatfmt(sds s, char const *fmt, ...); +sds sdstrim(sds s, const char *cset); +int sdsrange(sds s, ssize_t start, ssize_t end); +void sdsupdatelen(sds s); +void sdsclear(sds s); +int sdscmp(const sds s1, const sds s2); +sds *sdssplitlen(const char *s, int len, const char *sep, int seplen, int *count); +void sdsfreesplitres(sds *tokens, int count); +void sdstolower(sds s); +void sdstoupper(sds s); +sds sdsfromlonglong(long long value); +sds sdscatrepr(sds s, const char *p, size_t len); +sds *sdssplitargs(const char *line, int *argc); +sds sdsmapchars(sds s, const char *from, const char *to, size_t setlen); +sds sdsjoin(char **argv, int argc, char *sep); +sds sdsjoinsds(sds *argv, int argc, const char *sep, size_t seplen); /* Low level functions exposed to the user API */ -hisds hi_sdsMakeRoomFor(hisds s, size_t addlen); -void hi_sdsIncrLen(hisds s, int incr); -hisds hi_sdsRemoveFreeSpace(hisds s); -size_t hi_sdsAllocSize(hisds s); -void *hi_sdsAllocPtr(hisds s); +sds sdsMakeRoomFor(sds s, size_t addlen); +void sdsIncrLen(sds s, int incr); +sds sdsRemoveFreeSpace(sds s); +size_t sdsAllocSize(sds s); +void *sdsAllocPtr(sds s); /* Export the allocator used by SDS to the program using SDS. * Sometimes the program SDS is linked to, may use a different set of * allocators, but may want to allocate or free things that SDS will * respectively free or allocate. */ -void *hi_sds_malloc(size_t size); -void *hi_sds_realloc(void *ptr, size_t size); -void hi_sds_free(void *ptr); +void *sds_malloc(size_t size); +void *sds_realloc(void *ptr, size_t size); +void sds_free(void *ptr); #ifdef REDIS_TEST -int hi_sdsTest(int argc, char *argv[]); +int sdsTest(int argc, char *argv[]); #endif -#endif /* HIREDIS_SDS_H */ +#endif diff --git a/sdsalloc.h b/sdsalloc.h index c9dcc3df8..5538dd94c 100644 --- a/sdsalloc.h +++ b/sdsalloc.h @@ -39,6 +39,6 @@ #include "alloc.h" -#define hi_s_malloc hi_malloc -#define hi_s_realloc hi_realloc -#define hi_s_free hi_free +#define s_malloc hi_malloc +#define s_realloc hi_realloc +#define s_free hi_free diff --git a/sdscompat.h b/sdscompat.h deleted file mode 100644 index e5a2574f3..000000000 --- a/sdscompat.h +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (c) 2020, Michael Grunder - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of Redis nor the names of its contributors may be used - * to endorse or promote products derived from this software without - * specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -/* - * SDS compatibility header. - * - * This simple file maps sds types and calls to their unique hiredis symbol names. - * It's useful when we build Hiredis as a dependency of Redis and want to call - * Hiredis' sds symbols rather than the ones built into Redis, as the libraries - * have slightly diverged and could cause hard to track down ABI incompatibility - * bugs. - * - */ - -#ifndef HIREDIS_SDS_COMPAT -#define HIREDIS_SDS_COMPAT - -#define sds hisds - -#define sdslen hi_sdslen -#define sdsavail hi_sdsavail -#define sdssetlen hi_sdssetlen -#define sdsinclen hi_sdsinclen -#define sdsalloc hi_sdsalloc -#define sdssetalloc hi_sdssetalloc - -#define sdsAllocPtr hi_sdsAllocPtr -#define sdsAllocSize hi_sdsAllocSize -#define sdscat hi_sdscat -#define sdscatfmt hi_sdscatfmt -#define sdscatlen hi_sdscatlen -#define sdscatprintf hi_sdscatprintf -#define sdscatrepr hi_sdscatrepr -#define sdscatsds hi_sdscatsds -#define sdscatvprintf hi_sdscatvprintf -#define sdsclear hi_sdsclear -#define sdscmp hi_sdscmp -#define sdscpy hi_sdscpy -#define sdscpylen hi_sdscpylen -#define sdsdup hi_sdsdup -#define sdsempty hi_sdsempty -#define sds_free hi_sds_free -#define sdsfree hi_sdsfree -#define sdsfreesplitres hi_sdsfreesplitres -#define sdsfromlonglong hi_sdsfromlonglong -#define sdsgrowzero hi_sdsgrowzero -#define sdsIncrLen hi_sdsIncrLen -#define sdsjoin hi_sdsjoin -#define sdsjoinsds hi_sdsjoinsds -#define sdsll2str hi_sdsll2str -#define sdsMakeRoomFor hi_sdsMakeRoomFor -#define sds_malloc hi_sds_malloc -#define sdsmapchars hi_sdsmapchars -#define sdsnew hi_sdsnew -#define sdsnewlen hi_sdsnewlen -#define sdsrange hi_sdsrange -#define sds_realloc hi_sds_realloc -#define sdsRemoveFreeSpace hi_sdsRemoveFreeSpace -#define sdssplitargs hi_sdssplitargs -#define sdssplitlen hi_sdssplitlen -#define sdstolower hi_sdstolower -#define sdstoupper hi_sdstoupper -#define sdstrim hi_sdstrim -#define sdsull2str hi_sdsull2str -#define sdsupdatelen hi_sdsupdatelen - -#endif /* HIREDIS_SDS_COMPAT */ diff --git a/ssl.c b/ssl.c index fe9a2fdce..c581f63dc 100644 --- a/ssl.c +++ b/ssl.c @@ -38,6 +38,7 @@ #include #ifdef _WIN32 #include +#include #else #include #endif @@ -182,6 +183,10 @@ const char *redisSSLContextGetError(redisSSLContextError error) return "Failed to load client certificate"; case REDIS_SSL_CTX_PRIVATE_KEY_LOAD_FAILED: return "Failed to load private key"; + case REDIS_SSL_CTX_OS_CERTSTORE_OPEN_FAILED: + return "Failed to open system certifcate store"; + case REDIS_SSL_CTX_OS_CERT_ADD_FAILED: + return "Failed to add CA certificates obtained from system to the SSL context"; default: return "Unknown error code"; } @@ -214,6 +219,11 @@ redisSSLContext *redisCreateSSLContext(const char *cacert_filename, const char * const char *cert_filename, const char *private_key_filename, const char *server_name, redisSSLContextError *error) { +#ifdef _WIN32 + HCERTSTORE win_store = NULL; + PCCERT_CONTEXT win_ctx = NULL; +#endif + redisSSLContext *ctx = hi_calloc(1, sizeof(redisSSLContext)); if (ctx == NULL) goto error; @@ -234,6 +244,31 @@ redisSSLContext *redisCreateSSLContext(const char *cacert_filename, const char * } if (capath || cacert_filename) { +#ifdef _WIN32 + if (0 == strcmp(cacert_filename, "wincert")) { + win_store = CertOpenSystemStore(NULL, "Root"); + if (!win_store) { + if (error) *error = REDIS_SSL_CTX_OS_CERTSTORE_OPEN_FAILED; + goto error; + } + X509_STORE* store = SSL_CTX_get_cert_store(ctx->ssl_ctx); + while (win_ctx = CertEnumCertificatesInStore(win_store, win_ctx)) { + X509* x509 = NULL; + x509 = d2i_X509(NULL, (const unsigned char**)&win_ctx->pbCertEncoded, win_ctx->cbCertEncoded); + if (x509) { + if ((1 != X509_STORE_add_cert(store, x509)) || + (1 != SSL_CTX_add_client_CA(ctx->ssl_ctx, x509))) + { + if (error) *error = REDIS_SSL_CTX_OS_CERT_ADD_FAILED; + goto error; + } + X509_free(x509); + } + } + CertFreeCertificateContext(win_ctx); + CertCloseStore(win_store, 0); + } else +#endif if (!SSL_CTX_load_verify_locations(ctx->ssl_ctx, cacert_filename, capath)) { if (error) *error = REDIS_SSL_CTX_CA_CERT_LOAD_FAILED; goto error; @@ -257,6 +292,10 @@ redisSSLContext *redisCreateSSLContext(const char *cacert_filename, const char * return ctx; error: +#ifdef _WIN32 + CertFreeCertificateContext(win_ctx); + CertCloseStore(win_store, 0); +#endif redisFreeSSLContext(ctx); return NULL; } @@ -353,7 +392,11 @@ int redisInitiateSSLWithContext(redisContext *c, redisSSLContext *redis_ssl_ctx) } } - return redisSSLConnect(c, ssl); + if (redisSSLConnect(c, ssl) != REDIS_OK) { + goto error; + } + + return REDIS_OK; error: if (ssl) @@ -437,7 +480,7 @@ static ssize_t redisSSLRead(redisContext *c, char *buf, size_t bufcap) { static ssize_t redisSSLWrite(redisContext *c) { redisSSL *rssl = c->privctx; - size_t len = rssl->lastLen ? rssl->lastLen : hi_sdslen(c->obuf); + size_t len = rssl->lastLen ? rssl->lastLen : sdslen(c->obuf); int rv = SSL_write(rssl->ssl, c->obuf, len); if (rv > 0) { diff --git a/test.c b/test.c index 012b6ad61..f991ef1e7 100644 --- a/test.c +++ b/test.c @@ -11,12 +11,17 @@ #include #include #include +#include #include "hiredis.h" #include "async.h" #ifdef HIREDIS_TEST_SSL #include "hiredis_ssl.h" #endif +#ifdef HIREDIS_TEST_ASYNC +#include "adapters/libevent.h" +#include +#endif #include "net.h" #include "win32.h" @@ -53,6 +58,13 @@ struct privdata { int dtor_counter; }; +struct pushCounters { + int nil; + int str; +}; + +static int insecure_calloc_calls; + #ifdef HIREDIS_TEST_SSL redisSSLContext *_ssl_ctx = NULL; #endif @@ -340,21 +352,21 @@ static void test_format_commands(void) { len == 4+4+(3+2)+4+(7+2)+4+(3+2)); hi_free(cmd); - hisds sds_cmd; + sds sds_cmd; sds_cmd = NULL; - test("Format command into hisds by passing argc/argv without lengths: "); + test("Format command into sds by passing argc/argv without lengths: "); len = redisFormatSdsCommandArgv(&sds_cmd,argc,argv,NULL); test_cond(strncmp(sds_cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 && len == 4+4+(3+2)+4+(3+2)+4+(3+2)); - hi_sdsfree(sds_cmd); + sdsfree(sds_cmd); sds_cmd = NULL; - test("Format command into hisds by passing argc/argv with lengths: "); + test("Format command into sds by passing argc/argv with lengths: "); len = redisFormatSdsCommandArgv(&sds_cmd,argc,argv,lens); test_cond(strncmp(sds_cmd,"*3\r\n$3\r\nSET\r\n$7\r\nfoo\0xxx\r\n$3\r\nbar\r\n",len) == 0 && len == 4+4+(3+2)+4+(7+2)+4+(3+2)); - hi_sdsfree(sds_cmd); + sdsfree(sds_cmd); } static void test_append_formatted_commands(struct config config) { @@ -493,6 +505,20 @@ static void test_reply_reader(void) { freeReplyObject(reply); redisReaderFree(reader); + test("Multi-bulk never overflows regardless of maxelements: "); + size_t bad_mbulk_len = (SIZE_MAX / sizeof(void *)) + 3; + char bad_mbulk_reply[100]; + snprintf(bad_mbulk_reply, sizeof(bad_mbulk_reply), "*%llu\r\n+asdf\r\n", + (unsigned long long) bad_mbulk_len); + + reader = redisReaderCreate(); + reader->maxelements = 0; /* Don't rely on default limit */ + redisReaderFeed(reader, bad_mbulk_reply, strlen(bad_mbulk_reply)); + ret = redisReaderGetReply(reader,&reply); + test_cond(ret == REDIS_ERR && strcasecmp(reader->errstr, "Out of memory") == 0); + freeReplyObject(reply); + redisReaderFree(reader); + #if LLONG_MAX > SIZE_MAX test("Set error when array > SIZE_MAX: "); reader = redisReaderCreate(); @@ -578,6 +604,147 @@ static void test_reply_reader(void) { ((redisReply*)reply)->element[1]->integer == 42); freeReplyObject(reply); redisReaderFree(reader); + + test("Can parse RESP3 doubles: "); + reader = redisReaderCreate(); + redisReaderFeed(reader, ",3.14159265358979323846\r\n",25); + ret = redisReaderGetReply(reader,&reply); + test_cond(ret == REDIS_OK && + ((redisReply*)reply)->type == REDIS_REPLY_DOUBLE && + fabs(((redisReply*)reply)->dval - 3.14159265358979323846) < 0.00000001 && + ((redisReply*)reply)->len == 22 && + strcmp(((redisReply*)reply)->str, "3.14159265358979323846") == 0); + freeReplyObject(reply); + redisReaderFree(reader); + + test("Set error on invalid RESP3 double: "); + reader = redisReaderCreate(); + redisReaderFeed(reader, ",3.14159\000265358979323846\r\n",26); + ret = redisReaderGetReply(reader,&reply); + test_cond(ret == REDIS_ERR && + strcasecmp(reader->errstr,"Bad double value") == 0); + freeReplyObject(reply); + redisReaderFree(reader); + + test("Correctly parses RESP3 double INFINITY: "); + reader = redisReaderCreate(); + redisReaderFeed(reader, ",inf\r\n",6); + ret = redisReaderGetReply(reader,&reply); + test_cond(ret == REDIS_OK && + ((redisReply*)reply)->type == REDIS_REPLY_DOUBLE && + isinf(((redisReply*)reply)->dval) && + ((redisReply*)reply)->dval > 0); + freeReplyObject(reply); + redisReaderFree(reader); + + test("Set error when RESP3 double is NaN: "); + reader = redisReaderCreate(); + redisReaderFeed(reader, ",nan\r\n",6); + ret = redisReaderGetReply(reader,&reply); + test_cond(ret == REDIS_ERR && + strcasecmp(reader->errstr,"Bad double value") == 0); + freeReplyObject(reply); + redisReaderFree(reader); + + test("Can parse RESP3 nil: "); + reader = redisReaderCreate(); + redisReaderFeed(reader, "_\r\n",3); + ret = redisReaderGetReply(reader,&reply); + test_cond(ret == REDIS_OK && + ((redisReply*)reply)->type == REDIS_REPLY_NIL); + freeReplyObject(reply); + redisReaderFree(reader); + + test("Set error on invalid RESP3 nil: "); + reader = redisReaderCreate(); + redisReaderFeed(reader, "_nil\r\n",6); + ret = redisReaderGetReply(reader,&reply); + test_cond(ret == REDIS_ERR && + strcasecmp(reader->errstr,"Bad nil value") == 0); + freeReplyObject(reply); + redisReaderFree(reader); + + test("Can parse RESP3 bool (true): "); + reader = redisReaderCreate(); + redisReaderFeed(reader, "#t\r\n",4); + ret = redisReaderGetReply(reader,&reply); + test_cond(ret == REDIS_OK && + ((redisReply*)reply)->type == REDIS_REPLY_BOOL && + ((redisReply*)reply)->integer); + freeReplyObject(reply); + redisReaderFree(reader); + + test("Can parse RESP3 bool (false): "); + reader = redisReaderCreate(); + redisReaderFeed(reader, "#f\r\n",4); + ret = redisReaderGetReply(reader,&reply); + test_cond(ret == REDIS_OK && + ((redisReply*)reply)->type == REDIS_REPLY_BOOL && + !((redisReply*)reply)->integer); + freeReplyObject(reply); + redisReaderFree(reader); + + test("Set error on invalid RESP3 bool: "); + reader = redisReaderCreate(); + redisReaderFeed(reader, "#foobar\r\n",9); + ret = redisReaderGetReply(reader,&reply); + test_cond(ret == REDIS_ERR && + strcasecmp(reader->errstr,"Bad bool value") == 0); + freeReplyObject(reply); + redisReaderFree(reader); + + test("Can parse RESP3 map: "); + reader = redisReaderCreate(); + redisReaderFeed(reader, "%2\r\n+first\r\n:123\r\n$6\r\nsecond\r\n#t\r\n",34); + ret = redisReaderGetReply(reader,&reply); + test_cond(ret == REDIS_OK && + ((redisReply*)reply)->type == REDIS_REPLY_MAP && + ((redisReply*)reply)->elements == 4 && + ((redisReply*)reply)->element[0]->type == REDIS_REPLY_STATUS && + ((redisReply*)reply)->element[0]->len == 5 && + !strcmp(((redisReply*)reply)->element[0]->str,"first") && + ((redisReply*)reply)->element[1]->type == REDIS_REPLY_INTEGER && + ((redisReply*)reply)->element[1]->integer == 123 && + ((redisReply*)reply)->element[2]->type == REDIS_REPLY_STRING && + ((redisReply*)reply)->element[2]->len == 6 && + !strcmp(((redisReply*)reply)->element[2]->str,"second") && + ((redisReply*)reply)->element[3]->type == REDIS_REPLY_BOOL && + ((redisReply*)reply)->element[3]->integer); + freeReplyObject(reply); + redisReaderFree(reader); + + test("Can parse RESP3 set: "); + reader = redisReaderCreate(); + redisReaderFeed(reader, "~5\r\n+orange\r\n$5\r\napple\r\n#f\r\n:100\r\n:999\r\n",40); + ret = redisReaderGetReply(reader,&reply); + test_cond(ret == REDIS_OK && + ((redisReply*)reply)->type == REDIS_REPLY_SET && + ((redisReply*)reply)->elements == 5 && + ((redisReply*)reply)->element[0]->type == REDIS_REPLY_STATUS && + ((redisReply*)reply)->element[0]->len == 6 && + !strcmp(((redisReply*)reply)->element[0]->str,"orange") && + ((redisReply*)reply)->element[1]->type == REDIS_REPLY_STRING && + ((redisReply*)reply)->element[1]->len == 5 && + !strcmp(((redisReply*)reply)->element[1]->str,"apple") && + ((redisReply*)reply)->element[2]->type == REDIS_REPLY_BOOL && + !((redisReply*)reply)->element[2]->integer && + ((redisReply*)reply)->element[3]->type == REDIS_REPLY_INTEGER && + ((redisReply*)reply)->element[3]->integer == 100 && + ((redisReply*)reply)->element[4]->type == REDIS_REPLY_INTEGER && + ((redisReply*)reply)->element[4]->integer == 999); + freeReplyObject(reply); + redisReaderFree(reader); + + test("Can parse RESP3 bignum: "); + reader = redisReaderCreate(); + redisReaderFeed(reader,"(3492890328409238509324850943850943825024385\r\n",46); + ret = redisReaderGetReply(reader,&reply); + test_cond(ret == REDIS_OK && + ((redisReply*)reply)->type == REDIS_REPLY_BIGNUM && + ((redisReply*)reply)->len == 43 && + !strcmp(((redisReply*)reply)->str,"3492890328409238509324850943850943825024385")); + freeReplyObject(reply); + redisReaderFree(reader); } static void test_free_null(void) { @@ -604,6 +771,13 @@ static void *hi_calloc_fail(size_t nmemb, size_t size) { return NULL; } +static void *hi_calloc_insecure(size_t nmemb, size_t size) { + (void)nmemb; + (void)size; + insecure_calloc_calls++; + return (void*)0xdeadc0de; +} + static void *hi_realloc_fail(void *ptr, size_t size) { (void)ptr; (void)size; @@ -611,6 +785,8 @@ static void *hi_realloc_fail(void *ptr, size_t size) { } static void test_allocator_injection(void) { + void *ptr; + hiredisAllocFuncs ha = { .mallocFn = hi_malloc_fail, .callocFn = hi_calloc_fail, @@ -630,6 +806,13 @@ static void test_allocator_injection(void) { redisReader *reader = redisReaderCreate(); test_cond(reader == NULL); + /* Make sure hiredis itself protects against a non-overflow checking calloc */ + test("hiredis calloc wrapper protects against overflow: "); + ha.callocFn = hi_calloc_insecure; + hiredisSetAllocators(&ha); + ptr = hi_calloc((SIZE_MAX / sizeof(void*)) + 3, sizeof(void*)); + test_cond(ptr == NULL && insecure_calloc_calls == 0); + // Return allocators to default hiredisResetAllocators(); } @@ -677,11 +860,25 @@ static void test_blocking_connection_errors(void) { #endif } -/* Dummy push handler */ -void push_handler(void *privdata, void *reply) { - int *counter = privdata; +/* Test push handler */ +void push_handler(void *privdata, void *r) { + struct pushCounters *pcounts = privdata; + redisReply *reply = r, *payload; + + assert(reply && reply->type == REDIS_REPLY_PUSH && reply->elements == 2); + + payload = reply->element[1]; + if (payload->type == REDIS_REPLY_ARRAY) { + payload = payload->element[0]; + } + + if (payload->type == REDIS_REPLY_STRING) { + pcounts->str++; + } else if (payload->type == REDIS_REPLY_NIL) { + pcounts->nil++; + } + freeReplyObject(reply); - *counter += 1; } /* Dummy function just to test setting a callback with redisOptions */ @@ -691,16 +888,16 @@ void push_handler_async(redisAsyncContext *ac, void *reply) { } static void test_resp3_push_handler(redisContext *c) { + struct pushCounters pc = {0}; redisPushFn *old = NULL; redisReply *reply; void *privdata; - int n = 0; /* Switch to RESP3 and turn on client tracking */ send_hello(c, 3); send_client_tracking(c, "ON"); privdata = c->privdata; - c->privdata = &n; + c->privdata = &pc; reply = redisCommand(c, "GET key:0"); assert(reply != NULL); @@ -717,7 +914,12 @@ static void test_resp3_push_handler(redisContext *c) { old = redisSetPushCallback(c, push_handler); test("We can set a custom RESP3 PUSH handler: "); reply = redisCommand(c, "SET key:0 val:0"); - test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS && n == 1); + test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS && pc.str == 1); + freeReplyObject(reply); + + test("We properly handle a NIL invalidation payload: "); + reply = redisCommand(c, "FLUSHDB"); + test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS && pc.nil == 1); freeReplyObject(reply); /* Unset the push callback and generate an invalidate message making @@ -1245,6 +1447,440 @@ static void test_throughput(struct config config) { // redisFree(c); // } +#ifdef HIREDIS_TEST_ASYNC +struct event_base *base; + +typedef struct TestState { + redisOptions *options; + int checkpoint; + int resp3; + int disconnect; +} TestState; + +/* Helper to disconnect and stop event loop */ +void async_disconnect(redisAsyncContext *ac) { + redisAsyncDisconnect(ac); + event_base_loopbreak(base); +} + +/* Testcase timeout, will trigger a failure */ +void timeout_cb(int fd, short event, void *arg) { + (void) fd; (void) event; (void) arg; + printf("Timeout in async testing!\n"); + exit(1); +} + +/* Unexpected call, will trigger a failure */ +void unexpected_cb(redisAsyncContext *ac, void *r, void *privdata) { + (void) ac; (void) r; + printf("Unexpected call: %s\n",(char*)privdata); + exit(1); +} + +/* Helper function to publish a message via own client. */ +void publish_msg(redisOptions *options, const char* channel, const char* msg) { + redisContext *c = redisConnectWithOptions(options); + assert(c != NULL); + redisReply *reply = redisCommand(c,"PUBLISH %s %s",channel,msg); + assert(reply->type == REDIS_REPLY_INTEGER && reply->integer == 1); + freeReplyObject(reply); + disconnect(c, 0); +} + +/* Expect a reply of type INTEGER */ +void integer_cb(redisAsyncContext *ac, void *r, void *privdata) { + redisReply *reply = r; + TestState *state = privdata; + assert(reply != NULL && reply->type == REDIS_REPLY_INTEGER); + state->checkpoint++; + if (state->disconnect) async_disconnect(ac); +} + +/* Subscribe callback for test_pubsub_handling and test_pubsub_handling_resp3: + * - a published message triggers an unsubscribe + * - a command is sent before the unsubscribe response is received. */ +void subscribe_cb(redisAsyncContext *ac, void *r, void *privdata) { + redisReply *reply = r; + TestState *state = privdata; + + assert(reply != NULL && + reply->type == (state->resp3 ? REDIS_REPLY_PUSH : REDIS_REPLY_ARRAY) && + reply->elements == 3); + + if (strcmp(reply->element[0]->str,"subscribe") == 0) { + assert(strcmp(reply->element[1]->str,"mychannel") == 0 && + reply->element[2]->str == NULL); + publish_msg(state->options,"mychannel","Hello!"); + } else if (strcmp(reply->element[0]->str,"message") == 0) { + assert(strcmp(reply->element[1]->str,"mychannel") == 0 && + strcmp(reply->element[2]->str,"Hello!") == 0); + state->checkpoint++; + + /* Unsubscribe after receiving the published message. Send unsubscribe + * which should call the callback registered during subscribe */ + redisAsyncCommand(ac,unexpected_cb, + (void*)"unsubscribe should call subscribe_cb()", + "unsubscribe"); + /* Send a regular command after unsubscribing, then disconnect */ + state->disconnect = 1; + redisAsyncCommand(ac,integer_cb,state,"LPUSH mylist foo"); + + } else if (strcmp(reply->element[0]->str,"unsubscribe") == 0) { + assert(strcmp(reply->element[1]->str,"mychannel") == 0 && + reply->element[2]->str == NULL); + } else { + printf("Unexpected pubsub command: %s\n", reply->element[0]->str); + exit(1); + } +} + +/* Expect a reply of type ARRAY */ +void array_cb(redisAsyncContext *ac, void *r, void *privdata) { + redisReply *reply = r; + TestState *state = privdata; + assert(reply != NULL && reply->type == REDIS_REPLY_ARRAY); + state->checkpoint++; + if (state->disconnect) async_disconnect(ac); +} + +/* Expect a NULL reply */ +void null_cb(redisAsyncContext *ac, void *r, void *privdata) { + (void) ac; + assert(r == NULL); + TestState *state = privdata; + state->checkpoint++; +} + +static void test_pubsub_handling(struct config config) { + test("Subscribe, handle published message and unsubscribe: "); + /* Setup event dispatcher with a testcase timeout */ + base = event_base_new(); + struct event *timeout = evtimer_new(base, timeout_cb, NULL); + assert(timeout != NULL); + + evtimer_assign(timeout,base,timeout_cb,NULL); + struct timeval timeout_tv = {.tv_sec = 10}; + evtimer_add(timeout, &timeout_tv); + + /* Connect */ + redisOptions options = get_redis_tcp_options(config); + redisAsyncContext *ac = redisAsyncConnectWithOptions(&options); + assert(ac != NULL && ac->err == 0); + redisLibeventAttach(ac,base); + + /* Start subscribe */ + TestState state = {.options = &options}; + redisAsyncCommand(ac,subscribe_cb,&state,"subscribe mychannel"); + + /* Make sure non-subscribe commands are handled */ + redisAsyncCommand(ac,array_cb,&state,"PING"); + + /* Start event dispatching loop */ + test_cond(event_base_dispatch(base) == 0); + event_free(timeout); + event_base_free(base); + + /* Verify test checkpoints */ + assert(state.checkpoint == 3); +} + +/* Unexpected push message, will trigger a failure */ +void unexpected_push_cb(redisAsyncContext *ac, void *r) { + (void) ac; (void) r; + printf("Unexpected call to the PUSH callback!\n"); + exit(1); +} + +static void test_pubsub_handling_resp3(struct config config) { + test("Subscribe, handle published message and unsubscribe using RESP3: "); + /* Setup event dispatcher with a testcase timeout */ + base = event_base_new(); + struct event *timeout = evtimer_new(base, timeout_cb, NULL); + assert(timeout != NULL); + + evtimer_assign(timeout,base,timeout_cb,NULL); + struct timeval timeout_tv = {.tv_sec = 10}; + evtimer_add(timeout, &timeout_tv); + + /* Connect */ + redisOptions options = get_redis_tcp_options(config); + redisAsyncContext *ac = redisAsyncConnectWithOptions(&options); + assert(ac != NULL && ac->err == 0); + redisLibeventAttach(ac,base); + + /* Not expecting any push messages in this test */ + redisAsyncSetPushCallback(ac, unexpected_push_cb); + + /* Switch protocol */ + redisAsyncCommand(ac,NULL,NULL,"HELLO 3"); + + /* Start subscribe */ + TestState state = {.options = &options, .resp3 = 1}; + redisAsyncCommand(ac,subscribe_cb,&state,"subscribe mychannel"); + + /* Make sure non-subscribe commands are handled in RESP3 */ + redisAsyncCommand(ac,integer_cb,&state,"LPUSH mylist foo"); + redisAsyncCommand(ac,integer_cb,&state,"LPUSH mylist foo"); + redisAsyncCommand(ac,integer_cb,&state,"LPUSH mylist foo"); + /* Handle an array with 3 elements as a non-subscribe command */ + redisAsyncCommand(ac,array_cb,&state,"LRANGE mylist 0 2"); + + /* Start event dispatching loop */ + test_cond(event_base_dispatch(base) == 0); + event_free(timeout); + event_base_free(base); + + /* Verify test checkpoints */ + assert(state.checkpoint == 6); +} + +/* Subscribe callback for test_command_timeout_during_pubsub: + * - a subscribe response triggers a published message + * - the published message triggers a command that times out + * - the command timeout triggers a disconnect */ +void subscribe_with_timeout_cb(redisAsyncContext *ac, void *r, void *privdata) { + redisReply *reply = r; + TestState *state = privdata; + + /* The non-clean disconnect should trigger the + * subscription callback with a NULL reply. */ + if (reply == NULL) { + state->checkpoint++; + event_base_loopbreak(base); + return; + } + + assert(reply->type == (state->resp3 ? REDIS_REPLY_PUSH : REDIS_REPLY_ARRAY) && + reply->elements == 3); + + if (strcmp(reply->element[0]->str,"subscribe") == 0) { + assert(strcmp(reply->element[1]->str,"mychannel") == 0 && + reply->element[2]->str == NULL); + publish_msg(state->options,"mychannel","Hello!"); + state->checkpoint++; + } else if (strcmp(reply->element[0]->str,"message") == 0) { + assert(strcmp(reply->element[1]->str,"mychannel") == 0 && + strcmp(reply->element[2]->str,"Hello!") == 0); + state->checkpoint++; + + /* Send a command that will trigger a timeout */ + redisAsyncCommand(ac,null_cb,state,"DEBUG SLEEP 3"); + redisAsyncCommand(ac,null_cb,state,"LPUSH mylist foo"); + } else { + printf("Unexpected pubsub command: %s\n", reply->element[0]->str); + exit(1); + } +} + +static void test_command_timeout_during_pubsub(struct config config) { + test("Command timeout during Pub/Sub: "); + /* Setup event dispatcher with a testcase timeout */ + base = event_base_new(); + struct event *timeout = evtimer_new(base,timeout_cb,NULL); + assert(timeout != NULL); + + evtimer_assign(timeout,base,timeout_cb,NULL); + struct timeval timeout_tv = {.tv_sec = 10}; + evtimer_add(timeout,&timeout_tv); + + /* Connect */ + redisOptions options = get_redis_tcp_options(config); + redisAsyncContext *ac = redisAsyncConnectWithOptions(&options); + assert(ac != NULL && ac->err == 0); + redisLibeventAttach(ac,base); + + /* Configure a command timout */ + struct timeval command_timeout = {.tv_sec = 2}; + redisAsyncSetTimeout(ac,command_timeout); + + /* Not expecting any push messages in this test */ + redisAsyncSetPushCallback(ac,unexpected_push_cb); + + /* Switch protocol */ + redisAsyncCommand(ac,NULL,NULL,"HELLO 3"); + + /* Start subscribe */ + TestState state = {.options = &options, .resp3 = 1}; + redisAsyncCommand(ac,subscribe_with_timeout_cb,&state,"subscribe mychannel"); + + /* Start event dispatching loop */ + assert(event_base_dispatch(base) == 0); + event_free(timeout); + event_base_free(base); + + /* Verify test checkpoints */ + test_cond(state.checkpoint == 5); +} + +/* Subscribe callback for test_pubsub_multiple_channels */ +void subscribe_channel_a_cb(redisAsyncContext *ac, void *r, void *privdata) { + redisReply *reply = r; + TestState *state = privdata; + + assert(reply != NULL && reply->type == REDIS_REPLY_ARRAY && + reply->elements == 3); + + if (strcmp(reply->element[0]->str,"subscribe") == 0) { + assert(strcmp(reply->element[1]->str,"A") == 0); + publish_msg(state->options,"A","Hello!"); + state->checkpoint++; + } else if (strcmp(reply->element[0]->str,"message") == 0) { + assert(strcmp(reply->element[1]->str,"A") == 0 && + strcmp(reply->element[2]->str,"Hello!") == 0); + state->checkpoint++; + + /* Unsubscribe to channels, including a channel X which we don't subscribe to */ + redisAsyncCommand(ac,unexpected_cb, + (void*)"unsubscribe should not call unexpected_cb()", + "unsubscribe B X A"); + /* Send a regular command after unsubscribing, then disconnect */ + state->disconnect = 1; + redisAsyncCommand(ac,integer_cb,state,"LPUSH mylist foo"); + } else if (strcmp(reply->element[0]->str,"unsubscribe") == 0) { + assert(strcmp(reply->element[1]->str,"A") == 0); + state->checkpoint++; + } else { + printf("Unexpected pubsub command: %s\n", reply->element[0]->str); + exit(1); + } +} + +/* Subscribe callback for test_pubsub_multiple_channels */ +void subscribe_channel_b_cb(redisAsyncContext *ac, void *r, void *privdata) { + redisReply *reply = r; + TestState *state = privdata; + + assert(reply != NULL && reply->type == REDIS_REPLY_ARRAY && + reply->elements == 3); + + if (strcmp(reply->element[0]->str,"subscribe") == 0) { + assert(strcmp(reply->element[1]->str,"B") == 0); + state->checkpoint++; + } else if (strcmp(reply->element[0]->str,"unsubscribe") == 0) { + assert(strcmp(reply->element[1]->str,"B") == 0); + state->checkpoint++; + } else { + printf("Unexpected pubsub command: %s\n", reply->element[0]->str); + exit(1); + } +} + +/* Test handling of multiple channels + * - subscribe to channel A and B + * - a published message on A triggers an unsubscribe of channel B, X and A + * where channel X is not subscribed to. + * - a command sent after unsubscribe triggers a disconnect */ +static void test_pubsub_multiple_channels(struct config config) { + test("Subscribe to multiple channels: "); + /* Setup event dispatcher with a testcase timeout */ + base = event_base_new(); + struct event *timeout = evtimer_new(base,timeout_cb,NULL); + assert(timeout != NULL); + + evtimer_assign(timeout,base,timeout_cb,NULL); + struct timeval timeout_tv = {.tv_sec = 10}; + evtimer_add(timeout,&timeout_tv); + + /* Connect */ + redisOptions options = get_redis_tcp_options(config); + redisAsyncContext *ac = redisAsyncConnectWithOptions(&options); + assert(ac != NULL && ac->err == 0); + redisLibeventAttach(ac,base); + + /* Not expecting any push messages in this test */ + redisAsyncSetPushCallback(ac,unexpected_push_cb); + + /* Start subscribing to two channels */ + TestState state = {.options = &options}; + redisAsyncCommand(ac,subscribe_channel_a_cb,&state,"subscribe A"); + redisAsyncCommand(ac,subscribe_channel_b_cb,&state,"subscribe B"); + + /* Start event dispatching loop */ + assert(event_base_dispatch(base) == 0); + event_free(timeout); + event_base_free(base); + + /* Verify test checkpoints */ + test_cond(state.checkpoint == 6); +} + +/* Command callback for test_monitor() */ +void monitor_cb(redisAsyncContext *ac, void *r, void *privdata) { + redisReply *reply = r; + TestState *state = privdata; + + /* NULL reply is received when BYE triggers a disconnect. */ + if (reply == NULL) { + event_base_loopbreak(base); + return; + } + + assert(reply != NULL && reply->type == REDIS_REPLY_STATUS); + state->checkpoint++; + + if (state->checkpoint == 1) { + /* Response from MONITOR */ + redisContext *c = redisConnectWithOptions(state->options); + assert(c != NULL); + redisReply *reply = redisCommand(c,"SET first 1"); + assert(reply->type == REDIS_REPLY_STATUS); + freeReplyObject(reply); + redisFree(c); + } else if (state->checkpoint == 2) { + /* Response for monitored command 'SET first 1' */ + assert(strstr(reply->str,"first") != NULL); + redisContext *c = redisConnectWithOptions(state->options); + assert(c != NULL); + redisReply *reply = redisCommand(c,"SET second 2"); + assert(reply->type == REDIS_REPLY_STATUS); + freeReplyObject(reply); + redisFree(c); + } else if (state->checkpoint == 3) { + /* Response for monitored command 'SET second 2' */ + assert(strstr(reply->str,"second") != NULL); + /* Send QUIT to disconnect */ + redisAsyncCommand(ac,NULL,NULL,"QUIT"); + } +} + +/* Test handling of the monitor command + * - sends MONITOR to enable monitoring. + * - sends SET commands via separate clients to be monitored. + * - sends QUIT to stop monitoring and disconnect. */ +static void test_monitor(struct config config) { + test("Enable monitoring: "); + /* Setup event dispatcher with a testcase timeout */ + base = event_base_new(); + struct event *timeout = evtimer_new(base, timeout_cb, NULL); + assert(timeout != NULL); + + evtimer_assign(timeout,base,timeout_cb,NULL); + struct timeval timeout_tv = {.tv_sec = 10}; + evtimer_add(timeout, &timeout_tv); + + /* Connect */ + redisOptions options = get_redis_tcp_options(config); + redisAsyncContext *ac = redisAsyncConnectWithOptions(&options); + assert(ac != NULL && ac->err == 0); + redisLibeventAttach(ac,base); + + /* Not expecting any push messages in this test */ + redisAsyncSetPushCallback(ac,unexpected_push_cb); + + /* Start monitor */ + TestState state = {.options = &options}; + redisAsyncCommand(ac,monitor_cb,&state,"monitor"); + + /* Start event dispatching loop */ + test_cond(event_base_dispatch(base) == 0); + event_free(timeout); + event_base_free(base); + + /* Verify test checkpoints */ + assert(state.checkpoint == 3); +} +#endif /* HIREDIS_TEST_ASYNC */ + int main(int argc, char **argv) { struct config cfg = { .tcp = { @@ -1363,6 +1999,24 @@ int main(int argc, char **argv) { } #endif +#ifdef HIREDIS_TEST_ASYNC + printf("\nTesting asynchronous API against TCP connection (%s:%d):\n", cfg.tcp.host, cfg.tcp.port); + cfg.type = CONN_TCP; + + int major; + redisContext *c = do_connect(cfg); + get_redis_version(c, &major, NULL); + disconnect(c, 0); + + test_pubsub_handling(cfg); + test_pubsub_multiple_channels(cfg); + test_monitor(cfg); + if (major >= 6) { + test_pubsub_handling_resp3(cfg); + test_command_timeout_during_pubsub(cfg); + } +#endif /* HIREDIS_TEST_ASYNC */ + if (test_inherit_fd) { printf("\nTesting against inherited fd (%s): ", cfg.unix_sock.path); if (test_unix_socket) {