Explorar o código

add hiredis-vip to controller build

Grant Limberg %!s(int64=5) %!d(string=hai) anos
pai
achega
acb4ef0f12
Modificáronse 48 ficheiros con 15788 adicións e 5 borrados
  1. 9 0
      controller/PostgreSQL.cpp
  2. 3 0
      controller/PostgreSQL.hpp
  3. 2 2
      ext/central-controller-docker/Dockerfile
  4. 7 0
      ext/hiredis-vip-0.3.0/.gitignore
  5. 16 0
      ext/hiredis-vip-0.3.0/.travis.yml
  6. 16 0
      ext/hiredis-vip-0.3.0/CHANGELOG.md
  7. 29 0
      ext/hiredis-vip-0.3.0/COPYING
  8. 205 0
      ext/hiredis-vip-0.3.0/Makefile
  9. 255 0
      ext/hiredis-vip-0.3.0/README.md
  10. 154 0
      ext/hiredis-vip-0.3.0/adapters/ae.h
  11. 153 0
      ext/hiredis-vip-0.3.0/adapters/glib.h
  12. 147 0
      ext/hiredis-vip-0.3.0/adapters/libev.h
  13. 135 0
      ext/hiredis-vip-0.3.0/adapters/libevent.h
  14. 122 0
      ext/hiredis-vip-0.3.0/adapters/libuv.h
  15. 341 0
      ext/hiredis-vip-0.3.0/adlist.c
  16. 93 0
      ext/hiredis-vip-0.3.0/adlist.h
  17. 691 0
      ext/hiredis-vip-0.3.0/async.c
  18. 130 0
      ext/hiredis-vip-0.3.0/async.h
  19. 1700 0
      ext/hiredis-vip-0.3.0/command.c
  20. 179 0
      ext/hiredis-vip-0.3.0/command.h
  21. 87 0
      ext/hiredis-vip-0.3.0/crc16.c
  22. 338 0
      ext/hiredis-vip-0.3.0/dict.c
  23. 126 0
      ext/hiredis-vip-0.3.0/dict.h
  24. 62 0
      ext/hiredis-vip-0.3.0/examples/example-ae.c
  25. 73 0
      ext/hiredis-vip-0.3.0/examples/example-glib.c
  26. 52 0
      ext/hiredis-vip-0.3.0/examples/example-libev.c
  27. 53 0
      ext/hiredis-vip-0.3.0/examples/example-libevent.c
  28. 53 0
      ext/hiredis-vip-0.3.0/examples/example-libuv.c
  29. 78 0
      ext/hiredis-vip-0.3.0/examples/example.c
  30. 23 0
      ext/hiredis-vip-0.3.0/fmacros.h
  31. 188 0
      ext/hiredis-vip-0.3.0/hiarray.c
  32. 56 0
      ext/hiredis-vip-0.3.0/hiarray.h
  33. 4747 0
      ext/hiredis-vip-0.3.0/hircluster.c
  34. 178 0
      ext/hiredis-vip-0.3.0/hircluster.h
  35. 1021 0
      ext/hiredis-vip-0.3.0/hiredis.c
  36. 221 0
      ext/hiredis-vip-0.3.0/hiredis.h
  37. 554 0
      ext/hiredis-vip-0.3.0/hiutil.c
  38. 265 0
      ext/hiredis-vip-0.3.0/hiutil.h
  39. 458 0
      ext/hiredis-vip-0.3.0/net.c
  40. 53 0
      ext/hiredis-vip-0.3.0/net.h
  41. 525 0
      ext/hiredis-vip-0.3.0/read.c
  42. 129 0
      ext/hiredis-vip-0.3.0/read.h
  43. 1095 0
      ext/hiredis-vip-0.3.0/sds.c
  44. 105 0
      ext/hiredis-vip-0.3.0/sds.h
  45. 806 0
      ext/hiredis-vip-0.3.0/test.c
  46. 42 0
      ext/hiredis-vip-0.3.0/win32.h
  47. 8 1
      make-linux.mk
  48. 5 2
      make-mac.mk

+ 9 - 0
controller/PostgreSQL.cpp

@@ -740,6 +740,11 @@ void PostgreSQL::_membersWatcher_RabbitMQ() {
 	}
 }
 
+void PostgreSQL::_membersWatcher_Reids() {
+	char buff[11] = {0};
+	
+}
+
 void PostgreSQL::networksDbWatcher()
 {
 	PGconn *conn = getPgConn(NO_OVERRIDE);
@@ -844,6 +849,10 @@ void PostgreSQL::_networksWatcher_RabbitMQ() {
 	}
 }
 
+void PostgreSQL::_networksWatcher_Redis() {
+
+}
+
 void PostgreSQL::commitThread()
 {
 	PGconn *conn = getPgConn();

+ 3 - 0
controller/PostgreSQL.hpp

@@ -64,6 +64,9 @@ private:
 	void _networksWatcher_Postgres(PGconn *conn);
 	void _networksWatcher_RabbitMQ();
 
+	void _membersWatcher_Reids();
+	void _networksWatcher_Redis();
+
 	void commitThread();
 	void onlineNotificationThread();
 

+ 2 - 2
ext/central-controller-docker/Dockerfile

@@ -5,7 +5,7 @@ MAINTAINER Adam Ierymekno <[email protected]>, Grant Limberg <grant.li
 ARG git_branch=master
 
 RUN yum update -y
-RUN yum install -y https://download.postgresql.org/pub/repos/yum/10/redhat/rhel-7-x86_64/pgdg-centos10-10-2.noarch.rpm
+RUN yum install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-7-x86_64/pgdg-redhat-repo-latest.noarch.rpm
 RUN yum -y install epel-release && yum -y update && yum clean all
 RUN yum groupinstall -y "Development Tools"
 RUN yum install -y bash postgresql10 postgresql10-devel libpqxx-devel glibc-static libstdc++-static clang jemalloc jemalloc-devel
@@ -16,7 +16,7 @@ ADD . /ZeroTierOne
 RUN cd ZeroTierOne && make clean && make central-controller
 
 FROM centos:7
-RUN yum install -y https://download.postgresql.org/pub/repos/yum/10/redhat/rhel-7-x86_64/pgdg-centos10-10-2.noarch.rpm && yum -y install epel-release && yum -y update && yum clean all
+RUN yum install -y yum install https://download.postgresql.org/pub/repos/yum/reporpms/EL-7-x86_64/pgdg-redhat-repo-latest.noarch.rpm && yum -y install epel-release && yum -y update && yum clean all
 RUN yum install -y jemalloc jemalloc-devel postgresql10
 
 COPY --from=builder /ZeroTierOne/zerotier-one /usr/local/bin/zerotier-one

+ 7 - 0
ext/hiredis-vip-0.3.0/.gitignore

@@ -0,0 +1,7 @@
+/hiredis-test
+/examples/hiredis-example*
+/*.o
+/*.so
+/*.dylib
+/*.a
+/*.pc

+ 16 - 0
ext/hiredis-vip-0.3.0/.travis.yml

@@ -0,0 +1,16 @@
+language: c
+compiler:
+  - gcc
+  - clang
+
+env:
+    - CFLAGS="-Werror"
+    - PRE="valgrind --track-origins=yes --leak-check=full"
+    - TARGET="32bit" TARGET_VARS="32bit-vars" CFLAGS="-Werror"
+    - TARGET="32bit" TARGET_VARS="32bit-vars" PRE="valgrind --track-origins=yes --leak-check=full"
+
+install:
+    - sudo apt-get update -qq
+    - sudo apt-get install libc6-dbg libc6-dev libc6-i686:i386 libc6-dev-i386 libc6-dbg:i386 valgrind -y
+
+script: make $TARGET CFLAGS="$CFLAGS" && make check PRE="$PRE" && make $TARGET_VARS hiredis-example

+ 16 - 0
ext/hiredis-vip-0.3.0/CHANGELOG.md

@@ -0,0 +1,16 @@
+### 0.3.0 - Dec 07, 2016
+
+* Support redisClustervCommand, redisClustervAppendCommand and redisClustervAsyncCommand api. (deep011)
+* Add flags HIRCLUSTER_FLAG_ADD_OPENSLOT and HIRCLUSTER_FLAG_ROUTE_USE_SLOTS. (deep011)
+* Support redisClusterCommandArgv related api. (deep011)
+* Fix some serious bugs. (deep011)
+
+### 0.2.1 - Nov 24, 2015
+
+This release support redis cluster api.
+
+* Add hiredis 0.3.1. (deep011)
+* Support cluster synchronous API. (deep011)
+* Support multi-key command(mget/mset/del) for redis cluster. (deep011)
+* Support cluster pipelining. (deep011)
+* Support cluster asynchronous API. (deep011)

+ 29 - 0
ext/hiredis-vip-0.3.0/COPYING

@@ -0,0 +1,29 @@
+Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com>
+Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com>
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice,
+  this list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+  this list of conditions and the following disclaimer in the documentation
+  and/or other materials provided with the distribution.
+
+* Neither the name of Redis nor the names of its contributors may be used
+  to endorse or promote products derived from this software without specific
+  prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ 205 - 0
ext/hiredis-vip-0.3.0/Makefile

@@ -0,0 +1,205 @@
+# Hiredis Makefile
+# Copyright (C) 2010-2011 Salvatore Sanfilippo <antirez at gmail dot com>
+# Copyright (C) 2010-2011 Pieter Noordhuis <pcnoordhuis at gmail dot com>
+# This file is released under the BSD license, see the COPYING file
+
+OBJ=net.o hiredis.o sds.o async.o read.o hiarray.o hiutil.o command.o crc16.o adlist.o hircluster.o
+EXAMPLES=hiredis-example hiredis-example-libevent hiredis-example-libev hiredis-example-glib
+TESTS=hiredis-test
+LIBNAME=libhiredis_vip
+PKGCONFNAME=hiredis.pc
+
+HIREDIS_VIP_MAJOR=$(shell grep HIREDIS_VIP_MAJOR hircluster.h | awk '{print $$3}')
+HIREDIS_VIP_MINOR=$(shell grep HIREDIS_VIP_MINOR hircluster.h | awk '{print $$3}')
+HIREDIS_VIP_PATCH=$(shell grep HIREDIS_VIP_PATCH hircluster.h | awk '{print $$3}')
+
+# Installation related variables and target
+PREFIX?=/usr/local
+INCLUDE_PATH?=include/hiredis-vip
+LIBRARY_PATH?=lib
+PKGCONF_PATH?=pkgconfig
+INSTALL_INCLUDE_PATH= $(DESTDIR)$(PREFIX)/$(INCLUDE_PATH)
+INSTALL_LIBRARY_PATH= $(DESTDIR)$(PREFIX)/$(LIBRARY_PATH)
+INSTALL_PKGCONF_PATH= $(INSTALL_LIBRARY_PATH)/$(PKGCONF_PATH)
+
+# redis-server configuration used for testing
+REDIS_PORT=56379
+REDIS_SERVER=redis-server
+define REDIS_TEST_CONFIG
+	daemonize yes
+	pidfile /tmp/hiredis-test-redis.pid
+	port $(REDIS_PORT)
+	bind 127.0.0.1
+	unixsocket /tmp/hiredis-test-redis.sock
+endef
+export REDIS_TEST_CONFIG
+
+# Fallback to gcc when $CC is not in $PATH.
+CC:=$(shell sh -c 'type $(CC) >/dev/null 2>/dev/null && echo $(CC) || echo gcc')
+OPTIMIZATION?=-O3
+WARNINGS=-Wall -W -Wstrict-prototypes -Wwrite-strings
+DEBUG?= -g -ggdb
+REAL_CFLAGS=$(OPTIMIZATION) -fPIC $(CFLAGS) $(WARNINGS) $(DEBUG) $(ARCH)
+REAL_LDFLAGS=$(LDFLAGS) $(ARCH)
+
+DYLIBSUFFIX=so
+STLIBSUFFIX=a
+DYLIB_MINOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_VIP_MAJOR).$(HIREDIS_VIP_MINOR)
+DYLIB_MAJOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_VIP_MAJOR)
+DYLIBNAME=$(LIBNAME).$(DYLIBSUFFIX)
+DYLIB_MAKE_CMD=$(CC) -shared -Wl,-soname,$(DYLIB_MINOR_NAME) -o $(DYLIBNAME) $(LDFLAGS)
+STLIBNAME=$(LIBNAME).$(STLIBSUFFIX)
+STLIB_MAKE_CMD=ar rcs $(STLIBNAME)
+
+# Platform-specific overrides
+uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not')
+ifeq ($(uname_S),SunOS)
+  REAL_LDFLAGS+= -ldl -lnsl -lsocket
+  DYLIB_MAKE_CMD=$(CC) -G -o $(DYLIBNAME) -h $(DYLIB_MINOR_NAME) $(LDFLAGS)
+  INSTALL= cp -r
+endif
+ifeq ($(uname_S),Darwin)
+  DYLIBSUFFIX=dylib
+  DYLIB_MINOR_NAME=$(LIBNAME).$(HIREDIS_VIP_MAJOR).$(HIREDIS_VIP_MINOR).$(DYLIBSUFFIX)
+  DYLIB_MAJOR_NAME=$(LIBNAME).$(HIREDIS_VIP_MAJOR).$(DYLIBSUFFIX)
+  DYLIB_MAKE_CMD=$(CC) -shared -Wl,-install_name,$(DYLIB_MINOR_NAME) -o $(DYLIBNAME) $(LDFLAGS)
+endif
+
+all: $(DYLIBNAME) $(STLIBNAME) hiredis-test $(PKGCONFNAME)
+
+# Deps (use make dep to generate this)
+
+adlist.o: adlist.c adlist.h hiutil.h
+async.o: async.c fmacros.h async.h hiredis.h read.h sds.h net.h dict.c dict.h
+command.o: command.c command.h hiredis.h read.h sds.h adlist.h hiutil.h hiarray.h
+crc16.o: crc16.c hiutil.h
+dict.o: dict.c fmacros.h dict.h
+hiarray.o: hiarray.c hiarray.h hiutil.h
+hircluster.o: hircluster.c fmacros.h hircluster.h hiredis.h read.h sds.h adlist.h hiarray.h hiutil.h async.h command.h dict.c dict.h
+hiredis.o: hiredis.c fmacros.h hiredis.h read.h sds.h net.h
+hiutil.o: hiutil.c hiutil.h
+net.o: net.c fmacros.h net.h hiredis.h read.h sds.h
+read.o: read.c fmacros.h read.h sds.h
+sds.o: sds.c sds.h
+test.o: test.c fmacros.h hiredis.h read.h sds.h net.h
+
+$(DYLIBNAME): $(OBJ)
+	$(DYLIB_MAKE_CMD) $(OBJ)
+
+$(STLIBNAME): $(OBJ)
+	$(STLIB_MAKE_CMD) $(OBJ)
+
+dynamic: $(DYLIBNAME)
+static: $(STLIBNAME)
+
+# Binaries:
+hiredis-example-libevent: examples/example-libevent.c adapters/libevent.h $(STLIBNAME)
+	$(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< -levent $(STLIBNAME)
+
+hiredis-example-libev: examples/example-libev.c adapters/libev.h $(STLIBNAME)
+	$(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< -lev $(STLIBNAME)
+
+hiredis-example-glib: examples/example-glib.c adapters/glib.h $(STLIBNAME)
+	$(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) $(shell pkg-config --cflags --libs glib-2.0) -I. $< $(STLIBNAME)
+
+ifndef AE_DIR
+hiredis-example-ae:
+	@echo "Please specify AE_DIR (e.g. <redis repository>/src)"
+	@false
+else
+hiredis-example-ae: examples/example-ae.c adapters/ae.h $(STLIBNAME)
+	$(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. -I$(AE_DIR) $< $(AE_DIR)/ae.o $(AE_DIR)/zmalloc.o $(AE_DIR)/../deps/jemalloc/lib/libjemalloc.a -pthread $(STLIBNAME)
+endif
+
+ifndef LIBUV_DIR
+hiredis-example-libuv:
+	@echo "Please specify LIBUV_DIR (e.g. ../libuv/)"
+	@false
+else
+hiredis-example-libuv: examples/example-libuv.c adapters/libuv.h $(STLIBNAME)
+	$(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. -I$(LIBUV_DIR)/include $< $(LIBUV_DIR)/.libs/libuv.a -lpthread $(STLIBNAME)
+endif
+
+hiredis-example: examples/example.c $(STLIBNAME)
+	$(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< $(STLIBNAME)
+
+examples: $(EXAMPLES)
+
+hiredis-test: test.o $(STLIBNAME)
+
+hiredis-%: %.o $(STLIBNAME)
+	$(CC) $(REAL_CFLAGS) -o $@ $(REAL_LDFLAGS) $< $(STLIBNAME)
+
+test: hiredis-test
+	./hiredis-test
+
+check: hiredis-test
+	@echo "$$REDIS_TEST_CONFIG" | $(REDIS_SERVER) -
+	$(PRE) ./hiredis-test -h 127.0.0.1 -p $(REDIS_PORT) -s /tmp/hiredis-test-redis.sock || \
+			( kill `cat /tmp/hiredis-test-redis.pid` && false )
+	kill `cat /tmp/hiredis-test-redis.pid`
+
+.c.o:
+	$(CC) -std=c99 -pedantic -c $(REAL_CFLAGS) $<
+
+clean:
+	rm -rf $(DYLIBNAME) $(STLIBNAME) $(TESTS) $(PKGCONFNAME) examples/hiredis-example* *.o *.gcda *.gcno *.gcov
+
+dep:
+	$(CC) -MM *.c
+
+ifeq ($(uname_S),SunOS)
+  INSTALL?= cp -r
+endif
+
+INSTALL?= cp -a
+
+$(PKGCONFNAME): hiredis.h
+	@echo "Generating $@ for pkgconfig..."
+	@echo prefix=$(PREFIX) > $@
+	@echo exec_prefix=\$${prefix} >> $@
+	@echo libdir=$(PREFIX)/$(LIBRARY_PATH) >> $@
+	@echo includedir=$(PREFIX)/$(INCLUDE_PATH) >> $@
+	@echo >> $@
+	@echo Name: hiredis >> $@
+	@echo Description: Minimalistic C client library for Redis. >> $@
+	@echo Version: $(HIREDIS_VIP_MAJOR).$(HIREDIS_VIP_MINOR).$(HIREDIS_VIP_PATCH) >> $@
+	@echo Libs: -L\$${libdir} -lhiredis >> $@
+	@echo Cflags: -I\$${includedir} -D_FILE_OFFSET_BITS=64 >> $@
+
+install: $(DYLIBNAME) $(STLIBNAME) $(PKGCONFNAME)
+	mkdir -p $(INSTALL_INCLUDE_PATH) $(INSTALL_LIBRARY_PATH)
+	$(INSTALL) hiredis.h async.h read.h sds.h hiutil.h hiarray.h dict.h dict.c adlist.h fmacros.h hircluster.h adapters $(INSTALL_INCLUDE_PATH)
+	$(INSTALL) $(DYLIBNAME) $(INSTALL_LIBRARY_PATH)/$(DYLIB_MINOR_NAME)
+	cd $(INSTALL_LIBRARY_PATH) && ln -sf $(DYLIB_MINOR_NAME) $(DYLIB_MAJOR_NAME)
+	cd $(INSTALL_LIBRARY_PATH) && ln -sf $(DYLIB_MAJOR_NAME) $(DYLIBNAME)
+	$(INSTALL) $(STLIBNAME) $(INSTALL_LIBRARY_PATH)
+	mkdir -p $(INSTALL_PKGCONF_PATH)
+	$(INSTALL) $(PKGCONFNAME) $(INSTALL_PKGCONF_PATH)
+
+32bit:
+	@echo ""
+	@echo "WARNING: if this fails under Linux you probably need to install libc6-dev-i386"
+	@echo ""
+	$(MAKE) CFLAGS="-m32" LDFLAGS="-m32"
+
+32bit-vars:
+	$(eval CFLAGS=-m32)
+	$(eval LDFLAGS=-m32)
+
+gprof:
+	$(MAKE) CFLAGS="-pg" LDFLAGS="-pg"
+
+gcov:
+	$(MAKE) CFLAGS="-fprofile-arcs -ftest-coverage" LDFLAGS="-fprofile-arcs"
+
+coverage: gcov
+	make check
+	mkdir -p tmp/lcov
+	lcov -d . -c -o tmp/lcov/hiredis.info
+	genhtml --legend -o tmp/lcov/report tmp/lcov/hiredis.info
+
+noopt:
+	$(MAKE) OPTIMIZATION=""
+
+.PHONY: all test check clean dep install 32bit gprof gcov noopt

+ 255 - 0
ext/hiredis-vip-0.3.0/README.md

@@ -0,0 +1,255 @@
+
+# HIREDIS-VIP
+
+Hiredis-vip is a C client library for the [Redis](http://redis.io/) database.
+
+Hiredis-vip supported redis cluster.
+
+Hiredis-vip fully contained and based on [Hiredis](https://github.com/redis/hiredis) .
+
+## CLUSTER SUPPORT
+
+### FEATURES:
+
+* **`SUPPORT REDIS CLUSTER`**:
+    * Connect to redis cluster and run commands.
+
+* **`SUPPORT MULTI-KEY COMMAND`**:
+    * Support `MSET`, `MGET` and `DEL`.
+	
+* **`SUPPORT PIPELING`**:
+    * Support redis pipeline and can contain multi-key command like above.
+	
+* **`SUPPORT Asynchronous API`**:
+    * User can run commands with asynchronous mode.
+
+### CLUSTER API:
+
+```c
+redisClusterContext *redisClusterConnect(const char *addrs, int flags);
+redisClusterContext *redisClusterConnectWithTimeout(const char *addrs, const struct timeval tv, int flags);
+redisClusterContext *redisClusterConnectNonBlock(const char *addrs, int flags);
+void redisClusterFree(redisClusterContext *cc);
+void redisClusterSetMaxRedirect(redisClusterContext *cc, int max_redirect_count);
+void *redisClusterFormattedCommand(redisClusterContext *cc, char *cmd, int len);
+void *redisClustervCommand(redisClusterContext *cc, const char *format, va_list ap);
+void *redisClusterCommand(redisClusterContext *cc, const char *format, ...);
+void *redisClusterCommandArgv(redisClusterContext *cc, int argc, const char **argv, const size_t *argvlen);
+redisContext *ctx_get_by_node(struct cluster_node *node, const struct timeval *timeout, int flags);
+int redisClusterAppendFormattedCommand(redisClusterContext *cc, char *cmd, int len);
+int redisClustervAppendCommand(redisClusterContext *cc, const char *format, va_list ap);
+int redisClusterAppendCommand(redisClusterContext *cc, const char *format, ...);
+int redisClusterAppendCommandArgv(redisClusterContext *cc, int argc, const char **argv, const size_t *argvlen);
+int redisClusterGetReply(redisClusterContext *cc, void **reply);
+void redisClusterReset(redisClusterContext *cc);
+
+redisClusterAsyncContext *redisClusterAsyncConnect(const char *addrs, int flags);
+int redisClusterAsyncSetConnectCallback(redisClusterAsyncContext *acc, redisConnectCallback *fn);
+int redisClusterAsyncSetDisconnectCallback(redisClusterAsyncContext *acc, redisDisconnectCallback *fn);
+int redisClusterAsyncFormattedCommand(redisClusterAsyncContext *acc, redisClusterCallbackFn *fn, void *privdata, char *cmd, int len);
+int redisClustervAsyncCommand(redisClusterAsyncContext *acc, redisClusterCallbackFn *fn, void *privdata, const char *format, va_list ap);
+int redisClusterAsyncCommand(redisClusterAsyncContext *acc, redisClusterCallbackFn *fn, void *privdata, const char *format, ...);
+int redisClusterAsyncCommandArgv(redisClusterAsyncContext *acc, redisClusterCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen);
+
+void redisClusterAsyncDisconnect(redisClusterAsyncContext *acc);
+void redisClusterAsyncFree(redisClusterAsyncContext *acc);
+```
+
+## Quick usage
+
+If you want used but not read the follow, please reference the examples:
+https://github.com/vipshop/hiredis-vip/wiki
+
+## Cluster synchronous API
+
+To consume the synchronous API, there are only a few function calls that need to be introduced:
+
+```c
+redisClusterContext *redisClusterConnect(const char *addrs, int flags);
+void redisClusterSetMaxRedirect(redisClusterContext *cc, int max_redirect_count);
+void *redisClusterCommand(redisClusterContext *cc, const char *format, ...);
+void redisClusterFree(redisClusterContext *cc);
+```
+
+### Cluster connecting
+
+The function `redisClusterConnect` is used to create a so-called `redisClusterContext`. The
+context is where Hiredis-vip Cluster holds state for connections. The `redisClusterContext`
+struct has an integer `err` field that is non-zero when the connection is in
+an error state. The field `errstr` will contain a string with a description of
+the error.
+After trying to connect to Redis using `redisClusterContext` you should
+check the `err` field to see if establishing the connection was successful:
+```c
+redisClusterContext *cc = redisClusterConnect("127.0.0.1:6379", HIRCLUSTER_FLAG_NULL);
+if (cc != NULL && cc->err) {
+    printf("Error: %s\n", cc->errstr);
+    // handle error
+}
+```
+
+### Cluster sending commands
+
+The next that will be introduced is `redisClusterCommand`. 
+This function takes a format similar to printf. In the simplest form,
+it is used like this:
+```c
+reply = redisClusterCommand(clustercontext, "SET foo bar");
+```
+
+The specifier `%s` interpolates a string in the command, and uses `strlen` to
+determine the length of the string:
+```c
+reply = redisClusterCommand(clustercontext, "SET foo %s", value);
+```
+Internally, Hiredis-vip splits the command in different arguments and will
+convert it to the protocol used to communicate with Redis.
+One or more spaces separates arguments, so you can use the specifiers
+anywhere in an argument:
+```c
+reply = redisClusterCommand(clustercontext, "SET key:%s %s", myid, value);
+```
+
+### Cluster multi-key commands
+
+Hiredis-vip supports mget/mset/del multi-key commands.
+Those multi-key commands is highly effective.
+Millions of keys in one mget command just used several seconds.
+
+Example:
+```c
+reply = redisClusterCommand(clustercontext, "mget %s %s %s %s", key1, key2, key3, key4);
+```
+
+### Cluster cleaning up
+
+To disconnect and free the context the following function can be used:
+```c
+void redisClusterFree(redisClusterContext *cc);
+```
+This function immediately closes the socket and then frees the allocations done in
+creating the context.
+
+### Cluster pipelining
+
+The function `redisClusterGetReply` is exported as part of the Hiredis API and can be used 
+when a reply is expected on the socket. To pipeline commands, the only things that needs 
+to be done is filling up the output buffer. For this cause, two commands can be used that 
+are identical to the `redisClusterCommand` family, apart from not returning a reply:
+```c
+int redisClusterAppendCommand(redisClusterContext *cc, const char *format, ...);
+int redisClusterAppendCommandArgv(redisClusterContext *cc, int argc, const char **argv);
+```
+After calling either function one or more times, `redisClusterGetReply` can be used to receive the
+subsequent replies. The return value for this function is either `REDIS_OK` or `REDIS_ERR`, where
+the latter means an error occurred while reading a reply. Just as with the other commands,
+the `err` field in the context can be used to find out what the cause of this error is.
+```c
+void redisClusterReset(redisClusterContext *cc);
+```
+Warning: You must call `redisClusterReset` function after one pipelining anyway.
+
+The following examples shows a simple cluster pipeline:
+```c
+redisReply *reply;
+redisClusterAppendCommand(clusterContext,"SET foo bar");
+redisClusterAppendCommand(clusterContext,"GET foo");
+redisClusterGetReply(clusterContext,&reply); // reply for SET
+freeReplyObject(reply);
+redisClusterGetReply(clusterContext,&reply); // reply for GET
+freeReplyObject(reply);
+redisClusterReset(clusterContext);
+```
+
+## Cluster asynchronous API
+
+Hiredis-vip comes with an cluster asynchronous API that works easily with any event library.
+Now we just support and test for libevent and redis ae, if you need for other event libraries,
+please contact with us, and we will support it quickly.
+
+### Connecting
+
+The function `redisAsyncConnect` can be used to establish a non-blocking connection to
+Redis. It returns a pointer to the newly created `redisAsyncContext` struct. The `err` field
+should be checked after creation to see if there were errors creating the connection.
+Because the connection that will be created is non-blocking, the kernel is not able to
+instantly return if the specified host and port is able to accept a connection.
+```c
+redisClusterAsyncContext *acc = redisClusterAsyncConnect("127.0.0.1:6379", HIRCLUSTER_FLAG_NULL);
+if (acc->err) {
+    printf("Error: %s\n", acc->errstr);
+    // handle error
+}
+```
+
+The cluster asynchronous context can hold a disconnect callback function that is called when the
+connection is disconnected (either because of an error or per user request). This function should
+have the following prototype:
+```c
+void(const redisAsyncContext *c, int status);
+```
+On a disconnect, the `status` argument is set to `REDIS_OK` when disconnection was initiated by the
+user, or `REDIS_ERR` when the disconnection was caused by an error. When it is `REDIS_ERR`, the `err`
+field in the context can be accessed to find out the cause of the error.
+
+You not need to reconnect in the disconnect callback, hiredis-vip will reconnect this connection itself
+when commands come to this redis node.
+
+Setting the disconnect callback can only be done once per context. For subsequent calls it will
+return `REDIS_ERR`. The function to set the disconnect callback has the following prototype:
+```c
+int redisClusterAsyncSetDisconnectCallback(redisClusterAsyncContext *acc, redisDisconnectCallback *fn);
+```
+### Sending commands and their callbacks
+
+In an cluster asynchronous context, commands are automatically pipelined due to the nature of an event loop.
+Therefore, unlike the cluster synchronous API, there is only a single way to send commands.
+Because commands are sent to Redis cluster asynchronously, issuing a command requires a callback function
+that is called when the reply is received. Reply callbacks should have the following prototype:
+```c
+void(redisClusterAsyncContext *acc, void *reply, void *privdata);
+```
+The `privdata` argument can be used to curry arbitrary data to the callback from the point where
+the command is initially queued for execution.
+
+The functions that can be used to issue commands in an asynchronous context are:
+```c
+int redisClusterAsyncCommand(
+  redisClusterAsyncContext *acc, 
+  redisClusterCallbackFn *fn, 
+  void *privdata, const char *format, ...);
+```
+This function work like their blocking counterparts. The return value is `REDIS_OK` when the command
+was successfully added to the output buffer and `REDIS_ERR` otherwise. Example: when the connection
+is being disconnected per user-request, no new commands may be added to the output buffer and `REDIS_ERR` is
+returned on calls to the `redisClusterAsyncCommand` family.
+
+If the reply for a command with a `NULL` callback is read, it is immediately freed. When the callback
+for a command is non-`NULL`, the memory is freed immediately following the callback: the reply is only
+valid for the duration of the callback.
+
+All pending callbacks are called with a `NULL` reply when the context encountered an error.
+
+### Disconnecting
+
+An cluster asynchronous connection can be terminated using:
+```c
+void redisClusterAsyncDisconnect(redisClusterAsyncContext *acc);
+```
+When this function is called, the connection is **not** immediately terminated. Instead, new
+commands are no longer accepted and the connection is only terminated when all pending commands
+have been written to the socket, their respective replies have been read and their respective
+callbacks have been executed. After this, the disconnection callback is executed with the
+`REDIS_OK` status and the context object is freed.
+
+### Hooking it up to event library *X*
+
+There are a few hooks that need to be set on the cluster context object after it is created.
+See the `adapters/` directory for bindings to *ae* and *libevent*.
+
+## AUTHORS
+
+Hiredis-vip was maintained and used at vipshop(https://github.com/vipshop).
+The redis client library part in hiredis-vip is same as hiredis(https://github.com/redis/hiredis).
+The redis cluster client library part in hiredis-vip is written by deep(https://github.com/deep011).
+Hiredis-vip is released under the BSD license.

+ 154 - 0
ext/hiredis-vip-0.3.0/adapters/ae.h

@@ -0,0 +1,154 @@
+/*
+ * Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com>
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Redis nor the names of its contributors may be used
+ *     to endorse or promote products derived from this software without
+ *     specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __HIREDIS_AE_H__
+#define __HIREDIS_AE_H__
+#include <sys/types.h>
+#include <ae.h>
+#include "../hiredis.h"
+#include "../async.h"
+
+#if 1 //shenzheng 2015-11-5 redis cluster
+#include "../hircluster.h"
+#endif //shenzheng 2015-11-5 redis cluster
+
+typedef struct redisAeEvents {
+    redisAsyncContext *context;
+    aeEventLoop *loop;
+    int fd;
+    int reading, writing;
+} redisAeEvents;
+
+static void redisAeReadEvent(aeEventLoop *el, int fd, void *privdata, int mask) {
+    ((void)el); ((void)fd); ((void)mask);
+
+    redisAeEvents *e = (redisAeEvents*)privdata;
+    redisAsyncHandleRead(e->context);
+}
+
+static void redisAeWriteEvent(aeEventLoop *el, int fd, void *privdata, int mask) {
+    ((void)el); ((void)fd); ((void)mask);
+
+    redisAeEvents *e = (redisAeEvents*)privdata;
+    redisAsyncHandleWrite(e->context);
+}
+
+static void redisAeAddRead(void *privdata) {
+    redisAeEvents *e = (redisAeEvents*)privdata;
+    aeEventLoop *loop = e->loop;
+    if (!e->reading) {
+        e->reading = 1;
+        aeCreateFileEvent(loop,e->fd,AE_READABLE,redisAeReadEvent,e);
+    }
+}
+
+static void redisAeDelRead(void *privdata) {
+    redisAeEvents *e = (redisAeEvents*)privdata;
+    aeEventLoop *loop = e->loop;
+    if (e->reading) {
+        e->reading = 0;
+        aeDeleteFileEvent(loop,e->fd,AE_READABLE);
+    }
+}
+
+static void redisAeAddWrite(void *privdata) {
+    redisAeEvents *e = (redisAeEvents*)privdata;
+    aeEventLoop *loop = e->loop;
+    if (!e->writing) {
+        e->writing = 1;
+        aeCreateFileEvent(loop,e->fd,AE_WRITABLE,redisAeWriteEvent,e);
+    }
+}
+
+static void redisAeDelWrite(void *privdata) {
+    redisAeEvents *e = (redisAeEvents*)privdata;
+    aeEventLoop *loop = e->loop;
+    if (e->writing) {
+        e->writing = 0;
+        aeDeleteFileEvent(loop,e->fd,AE_WRITABLE);
+    }
+}
+
+static void redisAeCleanup(void *privdata) {
+    redisAeEvents *e = (redisAeEvents*)privdata;
+    redisAeDelRead(privdata);
+    redisAeDelWrite(privdata);
+    free(e);
+}
+
+static int redisAeAttach(aeEventLoop *loop, redisAsyncContext *ac) {
+    redisContext *c = &(ac->c);
+    redisAeEvents *e;
+
+    /* Nothing should be attached when something is already attached */
+    if (ac->ev.data != NULL)
+        return REDIS_ERR;
+
+    /* Create container for context and r/w events */
+    e = (redisAeEvents*)malloc(sizeof(*e));
+    e->context = ac;
+    e->loop = loop;
+    e->fd = c->fd;
+    e->reading = e->writing = 0;
+
+    /* Register functions to start/stop listening for events */
+    ac->ev.addRead = redisAeAddRead;
+    ac->ev.delRead = redisAeDelRead;
+    ac->ev.addWrite = redisAeAddWrite;
+    ac->ev.delWrite = redisAeDelWrite;
+    ac->ev.cleanup = redisAeCleanup;
+    ac->ev.data = e;
+
+    return REDIS_OK;
+}
+
+#if 1 //shenzheng 2015-11-5 redis cluster
+
+static int redisAeAttach_link(redisAsyncContext *ac, void *base)
+{
+	redisAeAttach((aeEventLoop *)base, ac);
+}
+
+static int redisClusterAeAttach(aeEventLoop *loop, redisClusterAsyncContext *acc) {
+
+	if(acc == NULL || loop == NULL)
+	{
+		return REDIS_ERR;
+	}
+
+	acc->adapter = loop;
+	acc->attach_fn = redisAeAttach_link;
+	
+    return REDIS_OK;
+}
+
+#endif //shenzheng 2015-11-5 redis cluster
+
+#endif

+ 153 - 0
ext/hiredis-vip-0.3.0/adapters/glib.h

@@ -0,0 +1,153 @@
+#ifndef __HIREDIS_GLIB_H__
+#define __HIREDIS_GLIB_H__
+
+#include <glib.h>
+
+#include "../hiredis.h"
+#include "../async.h"
+
+typedef struct
+{
+    GSource source;
+    redisAsyncContext *ac;
+    GPollFD poll_fd;
+} RedisSource;
+
+static void
+redis_source_add_read (gpointer data)
+{
+    RedisSource *source = data;
+    g_return_if_fail(source);
+    source->poll_fd.events |= G_IO_IN;
+    g_main_context_wakeup(g_source_get_context(data));
+}
+
+static void
+redis_source_del_read (gpointer data)
+{
+    RedisSource *source = data;
+    g_return_if_fail(source);
+    source->poll_fd.events &= ~G_IO_IN;
+    g_main_context_wakeup(g_source_get_context(data));
+}
+
+static void
+redis_source_add_write (gpointer data)
+{
+    RedisSource *source = data;
+    g_return_if_fail(source);
+    source->poll_fd.events |= G_IO_OUT;
+    g_main_context_wakeup(g_source_get_context(data));
+}
+
+static void
+redis_source_del_write (gpointer data)
+{
+    RedisSource *source = data;
+    g_return_if_fail(source);
+    source->poll_fd.events &= ~G_IO_OUT;
+    g_main_context_wakeup(g_source_get_context(data));
+}
+
+static void
+redis_source_cleanup (gpointer data)
+{
+    RedisSource *source = data;
+
+    g_return_if_fail(source);
+
+    redis_source_del_read(source);
+    redis_source_del_write(source);
+    /*
+     * It is not our responsibility to remove ourself from the
+     * current main loop. However, we will remove the GPollFD.
+     */
+    if (source->poll_fd.fd >= 0) {
+        g_source_remove_poll(data, &source->poll_fd);
+        source->poll_fd.fd = -1;
+    }
+}
+
+static gboolean
+redis_source_prepare (GSource *source,
+                      gint    *timeout_)
+{
+    RedisSource *redis = (RedisSource *)source;
+    *timeout_ = -1;
+    return !!(redis->poll_fd.events & redis->poll_fd.revents);
+}
+
+static gboolean
+redis_source_check (GSource *source)
+{
+    RedisSource *redis = (RedisSource *)source;
+    return !!(redis->poll_fd.events & redis->poll_fd.revents);
+}
+
+static gboolean
+redis_source_dispatch (GSource      *source,
+                       GSourceFunc   callback,
+                       gpointer      user_data)
+{
+    RedisSource *redis = (RedisSource *)source;
+
+    if ((redis->poll_fd.revents & G_IO_OUT)) {
+        redisAsyncHandleWrite(redis->ac);
+        redis->poll_fd.revents &= ~G_IO_OUT;
+    }
+
+    if ((redis->poll_fd.revents & G_IO_IN)) {
+        redisAsyncHandleRead(redis->ac);
+        redis->poll_fd.revents &= ~G_IO_IN;
+    }
+
+    if (callback) {
+        return callback(user_data);
+    }
+
+    return TRUE;
+}
+
+static void
+redis_source_finalize (GSource *source)
+{
+    RedisSource *redis = (RedisSource *)source;
+
+    if (redis->poll_fd.fd >= 0) {
+        g_source_remove_poll(source, &redis->poll_fd);
+        redis->poll_fd.fd = -1;
+    }
+}
+
+static GSource *
+redis_source_new (redisAsyncContext *ac)
+{
+    static GSourceFuncs source_funcs = {
+        .prepare  = redis_source_prepare,
+        .check     = redis_source_check,
+        .dispatch = redis_source_dispatch,
+        .finalize = redis_source_finalize,
+    };
+    redisContext *c = &ac->c;
+    RedisSource *source;
+
+    g_return_val_if_fail(ac != NULL, NULL);
+
+    source = (RedisSource *)g_source_new(&source_funcs, sizeof *source);
+    source->ac = ac;
+    source->poll_fd.fd = c->fd;
+    source->poll_fd.events = 0;
+    source->poll_fd.revents = 0;
+    g_source_add_poll((GSource *)source, &source->poll_fd);
+
+    ac->ev.addRead = redis_source_add_read;
+    ac->ev.delRead = redis_source_del_read;
+    ac->ev.addWrite = redis_source_add_write;
+    ac->ev.delWrite = redis_source_del_write;
+    ac->ev.cleanup = redis_source_cleanup;
+    ac->ev.data = source;
+
+    return (GSource *)source;
+}
+
+#endif /* __HIREDIS_GLIB_H__ */

+ 147 - 0
ext/hiredis-vip-0.3.0/adapters/libev.h

@@ -0,0 +1,147 @@
+/*
+ * Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com>
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Redis nor the names of its contributors may be used
+ *     to endorse or promote products derived from this software without
+ *     specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __HIREDIS_LIBEV_H__
+#define __HIREDIS_LIBEV_H__
+#include <stdlib.h>
+#include <sys/types.h>
+#include <ev.h>
+#include "../hiredis.h"
+#include "../async.h"
+
+typedef struct redisLibevEvents {
+    redisAsyncContext *context;
+    struct ev_loop *loop;
+    int reading, writing;
+    ev_io rev, wev;
+} redisLibevEvents;
+
+static void redisLibevReadEvent(EV_P_ ev_io *watcher, int revents) {
+#if EV_MULTIPLICITY
+    ((void)loop);
+#endif
+    ((void)revents);
+
+    redisLibevEvents *e = (redisLibevEvents*)watcher->data;
+    redisAsyncHandleRead(e->context);
+}
+
+static void redisLibevWriteEvent(EV_P_ ev_io *watcher, int revents) {
+#if EV_MULTIPLICITY
+    ((void)loop);
+#endif
+    ((void)revents);
+
+    redisLibevEvents *e = (redisLibevEvents*)watcher->data;
+    redisAsyncHandleWrite(e->context);
+}
+
+static void redisLibevAddRead(void *privdata) {
+    redisLibevEvents *e = (redisLibevEvents*)privdata;
+    struct ev_loop *loop = e->loop;
+    ((void)loop);
+    if (!e->reading) {
+        e->reading = 1;
+        ev_io_start(EV_A_ &e->rev);
+    }
+}
+
+static void redisLibevDelRead(void *privdata) {
+    redisLibevEvents *e = (redisLibevEvents*)privdata;
+    struct ev_loop *loop = e->loop;
+    ((void)loop);
+    if (e->reading) {
+        e->reading = 0;
+        ev_io_stop(EV_A_ &e->rev);
+    }
+}
+
+static void redisLibevAddWrite(void *privdata) {
+    redisLibevEvents *e = (redisLibevEvents*)privdata;
+    struct ev_loop *loop = e->loop;
+    ((void)loop);
+    if (!e->writing) {
+        e->writing = 1;
+        ev_io_start(EV_A_ &e->wev);
+    }
+}
+
+static void redisLibevDelWrite(void *privdata) {
+    redisLibevEvents *e = (redisLibevEvents*)privdata;
+    struct ev_loop *loop = e->loop;
+    ((void)loop);
+    if (e->writing) {
+        e->writing = 0;
+        ev_io_stop(EV_A_ &e->wev);
+    }
+}
+
+static void redisLibevCleanup(void *privdata) {
+    redisLibevEvents *e = (redisLibevEvents*)privdata;
+    redisLibevDelRead(privdata);
+    redisLibevDelWrite(privdata);
+    free(e);
+}
+
+static int redisLibevAttach(EV_P_ redisAsyncContext *ac) {
+    redisContext *c = &(ac->c);
+    redisLibevEvents *e;
+
+    /* Nothing should be attached when something is already attached */
+    if (ac->ev.data != NULL)
+        return REDIS_ERR;
+
+    /* Create container for context and r/w events */
+    e = (redisLibevEvents*)malloc(sizeof(*e));
+    e->context = ac;
+#if EV_MULTIPLICITY
+    e->loop = loop;
+#else
+    e->loop = NULL;
+#endif
+    e->reading = e->writing = 0;
+    e->rev.data = e;
+    e->wev.data = e;
+
+    /* Register functions to start/stop listening for events */
+    ac->ev.addRead = redisLibevAddRead;
+    ac->ev.delRead = redisLibevDelRead;
+    ac->ev.addWrite = redisLibevAddWrite;
+    ac->ev.delWrite = redisLibevDelWrite;
+    ac->ev.cleanup = redisLibevCleanup;
+    ac->ev.data = e;
+
+    /* Initialize read/write events */
+    ev_io_init(&e->rev,redisLibevReadEvent,c->fd,EV_READ);
+    ev_io_init(&e->wev,redisLibevWriteEvent,c->fd,EV_WRITE);
+    return REDIS_OK;
+}
+
+#endif

+ 135 - 0
ext/hiredis-vip-0.3.0/adapters/libevent.h

@@ -0,0 +1,135 @@
+/*
+ * Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com>
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Redis nor the names of its contributors may be used
+ *     to endorse or promote products derived from this software without
+ *     specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __HIREDIS_LIBEVENT_H__
+#define __HIREDIS_LIBEVENT_H__
+#include <event.h>
+#include "../hiredis.h"
+#include "../async.h"
+
+#if 1 //shenzheng 2015-9-21 redis cluster
+#include "../hircluster.h"
+#endif //shenzheng 2015-9-21 redis cluster
+
+typedef struct redisLibeventEvents {
+    redisAsyncContext *context;
+    struct event rev, wev;
+} redisLibeventEvents;
+
+static void redisLibeventReadEvent(int fd, short event, void *arg) {
+    ((void)fd); ((void)event);
+    redisLibeventEvents *e = (redisLibeventEvents*)arg;
+    redisAsyncHandleRead(e->context);
+}
+
+static void redisLibeventWriteEvent(int fd, short event, void *arg) {
+    ((void)fd); ((void)event);
+    redisLibeventEvents *e = (redisLibeventEvents*)arg;
+    redisAsyncHandleWrite(e->context);
+}
+
+static void redisLibeventAddRead(void *privdata) {
+    redisLibeventEvents *e = (redisLibeventEvents*)privdata;
+    event_add(&e->rev,NULL);
+}
+
+static void redisLibeventDelRead(void *privdata) {
+    redisLibeventEvents *e = (redisLibeventEvents*)privdata;
+    event_del(&e->rev);
+}
+
+static void redisLibeventAddWrite(void *privdata) {
+    redisLibeventEvents *e = (redisLibeventEvents*)privdata;
+    event_add(&e->wev,NULL);
+}
+
+static void redisLibeventDelWrite(void *privdata) {
+    redisLibeventEvents *e = (redisLibeventEvents*)privdata;
+    event_del(&e->wev);
+}
+
+static void redisLibeventCleanup(void *privdata) {
+    redisLibeventEvents *e = (redisLibeventEvents*)privdata;
+    event_del(&e->rev);
+    event_del(&e->wev);
+    free(e);
+}
+
+static int redisLibeventAttach(redisAsyncContext *ac, struct event_base *base) {
+    redisContext *c = &(ac->c);
+    redisLibeventEvents *e;
+
+    /* Nothing should be attached when something is already attached */
+    if (ac->ev.data != NULL)
+        return REDIS_ERR;
+
+    /* Create container for context and r/w events */
+    e = (redisLibeventEvents*)malloc(sizeof(*e));
+    e->context = ac;
+
+    /* Register functions to start/stop listening for events */
+    ac->ev.addRead = redisLibeventAddRead;
+    ac->ev.delRead = redisLibeventDelRead;
+    ac->ev.addWrite = redisLibeventAddWrite;
+    ac->ev.delWrite = redisLibeventDelWrite;
+    ac->ev.cleanup = redisLibeventCleanup;
+    ac->ev.data = e;
+
+    /* Initialize and install read/write events */
+    event_set(&e->rev,c->fd,EV_READ,redisLibeventReadEvent,e);
+    event_set(&e->wev,c->fd,EV_WRITE,redisLibeventWriteEvent,e);
+    event_base_set(base,&e->rev);
+    event_base_set(base,&e->wev);
+    return REDIS_OK;
+}
+
+#if 1 //shenzheng 2015-9-21 redis cluster
+
+static int redisLibeventAttach_link(redisAsyncContext *ac, void *base)
+{
+	redisLibeventAttach(ac, (struct event_base *)base);
+}
+
+static int redisClusterLibeventAttach(redisClusterAsyncContext *acc, struct event_base *base) {
+
+	if(acc == NULL || base == NULL)
+	{
+		return REDIS_ERR;
+	}
+
+	acc->adapter = base;
+	acc->attach_fn = redisLibeventAttach_link;
+	
+    return REDIS_OK;
+}
+
+#endif //shenzheng 2015-9-21 redis cluster
+
+#endif

+ 122 - 0
ext/hiredis-vip-0.3.0/adapters/libuv.h

@@ -0,0 +1,122 @@
+#ifndef __HIREDIS_LIBUV_H__
+#define __HIREDIS_LIBUV_H__
+#include <stdlib.h>
+#include <uv.h>
+#include "../hiredis.h"
+#include "../async.h"
+#include <string.h>
+
+typedef struct redisLibuvEvents {
+  redisAsyncContext* context;
+  uv_poll_t          handle;
+  int                events;
+} redisLibuvEvents;
+
+
+static void redisLibuvPoll(uv_poll_t* handle, int status, int events) {
+  redisLibuvEvents* p = (redisLibuvEvents*)handle->data;
+
+  if (status != 0) {
+    return;
+  }
+
+  if (events & UV_READABLE) {
+    redisAsyncHandleRead(p->context);
+  }
+  if (events & UV_WRITABLE) {
+    redisAsyncHandleWrite(p->context);
+  }
+}
+
+
+static void redisLibuvAddRead(void *privdata) {
+  redisLibuvEvents* p = (redisLibuvEvents*)privdata;
+
+  p->events |= UV_READABLE;
+
+  uv_poll_start(&p->handle, p->events, redisLibuvPoll);
+}
+
+
+static void redisLibuvDelRead(void *privdata) {
+  redisLibuvEvents* p = (redisLibuvEvents*)privdata;
+
+  p->events &= ~UV_READABLE;
+
+  if (p->events) {
+    uv_poll_start(&p->handle, p->events, redisLibuvPoll);
+  } else {
+    uv_poll_stop(&p->handle);
+  }
+}
+
+
+static void redisLibuvAddWrite(void *privdata) {
+  redisLibuvEvents* p = (redisLibuvEvents*)privdata;
+
+  p->events |= UV_WRITABLE;
+
+  uv_poll_start(&p->handle, p->events, redisLibuvPoll);
+}
+
+
+static void redisLibuvDelWrite(void *privdata) {
+  redisLibuvEvents* p = (redisLibuvEvents*)privdata;
+
+  p->events &= ~UV_WRITABLE;
+
+  if (p->events) {
+    uv_poll_start(&p->handle, p->events, redisLibuvPoll);
+  } else {
+    uv_poll_stop(&p->handle);
+  }
+}
+
+
+static void on_close(uv_handle_t* handle) {
+  redisLibuvEvents* p = (redisLibuvEvents*)handle->data;
+
+  free(p);
+}
+
+
+static void redisLibuvCleanup(void *privdata) {
+  redisLibuvEvents* p = (redisLibuvEvents*)privdata;
+
+  uv_close((uv_handle_t*)&p->handle, on_close);
+}
+
+
+static int redisLibuvAttach(redisAsyncContext* ac, uv_loop_t* loop) {
+  redisContext *c = &(ac->c);
+
+  if (ac->ev.data != NULL) {
+    return REDIS_ERR;
+  }
+
+  ac->ev.addRead  = redisLibuvAddRead;
+  ac->ev.delRead  = redisLibuvDelRead;
+  ac->ev.addWrite = redisLibuvAddWrite;
+  ac->ev.delWrite = redisLibuvDelWrite;
+  ac->ev.cleanup  = redisLibuvCleanup;
+
+  redisLibuvEvents* p = (redisLibuvEvents*)malloc(sizeof(*p));
+
+  if (!p) {
+    return REDIS_ERR;
+  }
+
+  memset(p, 0, sizeof(*p));
+
+  if (uv_poll_init(loop, &p->handle, c->fd) != 0) {
+    return REDIS_ERR;
+  }
+
+  ac->ev.data    = p;
+  p->handle.data = p;
+  p->context     = ac;
+
+  return REDIS_OK;
+}
+
+#endif

+ 341 - 0
ext/hiredis-vip-0.3.0/adlist.c

@@ -0,0 +1,341 @@
+/* adlist.c - A generic doubly linked list implementation
+ *
+ * Copyright (c) 2006-2010, Salvatore Sanfilippo <antirez at gmail dot com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Redis nor the names of its contributors may be used
+ *     to endorse or promote products derived from this software without
+ *     specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+
+#include <stdlib.h>
+#include "adlist.h"
+#include "hiutil.h"
+
+/* Create a new list. The created list can be freed with
+ * AlFreeList(), but private value of every node need to be freed
+ * by the user before to call AlFreeList().
+ *
+ * On error, NULL is returned. Otherwise the pointer to the new list. */
+hilist *listCreate(void)
+{
+    struct hilist *list;
+
+    if ((list = hi_alloc(sizeof(*list))) == NULL)
+        return NULL;
+    list->head = list->tail = NULL;
+    list->len = 0;
+    list->dup = NULL;
+    list->free = NULL;
+    list->match = NULL;
+    return list;
+}
+
+/* Free the whole list.
+ *
+ * This function can't fail. */
+void listRelease(hilist *list)
+{
+    unsigned long len;
+    listNode *current, *next;
+
+    current = list->head;
+    len = list->len;
+    while(len--) {
+        next = current->next;
+        if (list->free) list->free(current->value);
+        hi_free(current);
+        current = next;
+    }
+    hi_free(list);
+}
+
+/* Add a new node to the list, to head, containing the specified 'value'
+ * pointer as value.
+ *
+ * On error, NULL is returned and no operation is performed (i.e. the
+ * list remains unaltered).
+ * On success the 'list' pointer you pass to the function is returned. */
+hilist *listAddNodeHead(hilist *list, void *value)
+{
+    listNode *node;
+
+    if ((node = hi_alloc(sizeof(*node))) == NULL)
+        return NULL;
+    node->value = value;
+    if (list->len == 0) {
+        list->head = list->tail = node;
+        node->prev = node->next = NULL;
+    } else {
+        node->prev = NULL;
+        node->next = list->head;
+        list->head->prev = node;
+        list->head = node;
+    }
+    list->len++;
+    return list;
+}
+
+/* Add a new node to the list, to tail, containing the specified 'value'
+ * pointer as value.
+ *
+ * On error, NULL is returned and no operation is performed (i.e. the
+ * list remains unaltered).
+ * On success the 'list' pointer you pass to the function is returned. */
+hilist *listAddNodeTail(hilist *list, void *value)
+{
+    listNode *node;
+
+    if ((node = hi_alloc(sizeof(*node))) == NULL)
+        return NULL;
+    node->value = value;
+    if (list->len == 0) {
+        list->head = list->tail = node;
+        node->prev = node->next = NULL;
+    } else {
+        node->prev = list->tail;
+        node->next = NULL;
+        list->tail->next = node;
+        list->tail = node;
+    }
+    list->len++;
+    return list;
+}
+
+hilist *listInsertNode(hilist *list, listNode *old_node, void *value, int after) {
+    listNode *node;
+
+    if ((node = hi_alloc(sizeof(*node))) == NULL)
+        return NULL;
+    node->value = value;
+    if (after) {
+        node->prev = old_node;
+        node->next = old_node->next;
+        if (list->tail == old_node) {
+            list->tail = node;
+        }
+    } else {
+        node->next = old_node;
+        node->prev = old_node->prev;
+        if (list->head == old_node) {
+            list->head = node;
+        }
+    }
+    if (node->prev != NULL) {
+        node->prev->next = node;
+    }
+    if (node->next != NULL) {
+        node->next->prev = node;
+    }
+    list->len++;
+    return list;
+}
+
+/* Remove the specified node from the specified list.
+ * It's up to the caller to free the private value of the node.
+ *
+ * This function can't fail. */
+void listDelNode(hilist *list, listNode *node)
+{
+    if (node->prev)
+        node->prev->next = node->next;
+    else
+        list->head = node->next;
+    if (node->next)
+        node->next->prev = node->prev;
+    else
+        list->tail = node->prev;
+    if (list->free) list->free(node->value);
+    hi_free(node);
+    list->len--;
+}
+
+/* Returns a list iterator 'iter'. After the initialization every
+ * call to listNext() will return the next element of the list.
+ *
+ * This function can't fail. */
+listIter *listGetIterator(hilist *list, int direction)
+{
+    listIter *iter;
+
+    if ((iter = hi_alloc(sizeof(*iter))) == NULL) return NULL;
+    if (direction == AL_START_HEAD)
+        iter->next = list->head;
+    else
+        iter->next = list->tail;
+    iter->direction = direction;
+    return iter;
+}
+
+/* Release the iterator memory */
+void listReleaseIterator(listIter *iter) {
+    hi_free(iter);
+}
+
+/* Create an iterator in the list private iterator structure */
+void listRewind(hilist *list, listIter *li) {
+    li->next = list->head;
+    li->direction = AL_START_HEAD;
+}
+
+void listRewindTail(hilist *list, listIter *li) {
+    li->next = list->tail;
+    li->direction = AL_START_TAIL;
+}
+
+/* Return the next element of an iterator.
+ * It's valid to remove the currently returned element using
+ * listDelNode(), but not to remove other elements.
+ *
+ * The function returns a pointer to the next element of the list,
+ * or NULL if there are no more elements, so the classical usage patter
+ * is:
+ *
+ * iter = listGetIterator(list,<direction>);
+ * while ((node = listNext(iter)) != NULL) {
+ *     doSomethingWith(listNodeValue(node));
+ * }
+ *
+ * */
+listNode *listNext(listIter *iter)
+{
+    listNode *current = iter->next;
+
+    if (current != NULL) {
+        if (iter->direction == AL_START_HEAD)
+            iter->next = current->next;
+        else
+            iter->next = current->prev;
+    }
+    return current;
+}
+
+/* Duplicate the whole list. On out of memory NULL is returned.
+ * On success a copy of the original list is returned.
+ *
+ * The 'Dup' method set with listSetDupMethod() function is used
+ * to copy the node value. Otherwise the same pointer value of
+ * the original node is used as value of the copied node.
+ *
+ * The original list both on success or error is never modified. */
+hilist *listDup(hilist *orig)
+{
+    hilist *copy;
+    listIter *iter;
+    listNode *node;
+
+    if ((copy = listCreate()) == NULL)
+        return NULL;
+    copy->dup = orig->dup;
+    copy->free = orig->free;
+    copy->match = orig->match;
+    iter = listGetIterator(orig, AL_START_HEAD);
+    while((node = listNext(iter)) != NULL) {
+        void *value;
+
+        if (copy->dup) {
+            value = copy->dup(node->value);
+            if (value == NULL) {
+                listRelease(copy);
+                listReleaseIterator(iter);
+                return NULL;
+            }
+        } else
+            value = node->value;
+        if (listAddNodeTail(copy, value) == NULL) {
+            listRelease(copy);
+            listReleaseIterator(iter);
+            return NULL;
+        }
+    }
+    listReleaseIterator(iter);
+    return copy;
+}
+
+/* Search the list for a node matching a given key.
+ * The match is performed using the 'match' method
+ * set with listSetMatchMethod(). If no 'match' method
+ * is set, the 'value' pointer of every node is directly
+ * compared with the 'key' pointer.
+ *
+ * On success the first matching node pointer is returned
+ * (search starts from head). If no matching node exists
+ * NULL is returned. */
+listNode *listSearchKey(hilist *list, void *key)
+{
+    listIter *iter;
+    listNode *node;
+
+    iter = listGetIterator(list, AL_START_HEAD);
+    while((node = listNext(iter)) != NULL) {
+        if (list->match) {
+            if (list->match(node->value, key)) {
+                listReleaseIterator(iter);
+                return node;
+            }
+        } else {
+            if (key == node->value) {
+                listReleaseIterator(iter);
+                return node;
+            }
+        }
+    }
+    listReleaseIterator(iter);
+    return NULL;
+}
+
+/* Return the element at the specified zero-based index
+ * where 0 is the head, 1 is the element next to head
+ * and so on. Negative integers are used in order to count
+ * from the tail, -1 is the last element, -2 the penultimate
+ * and so on. If the index is out of range NULL is returned. */
+listNode *listIndex(hilist *list, long index) {
+    listNode *n;
+
+    if (index < 0) {
+        index = (-index)-1;
+        n = list->tail;
+        while(index-- && n) n = n->prev;
+    } else {
+        n = list->head;
+        while(index-- && n) n = n->next;
+    }
+    return n;
+}
+
+/* Rotate the list removing the tail node and inserting it to the head. */
+void listRotate(hilist *list) {
+    listNode *tail = list->tail;
+
+    if (listLength(list) <= 1) return;
+
+    /* Detach current tail */
+    list->tail = tail->prev;
+    list->tail->next = NULL;
+    /* Move it as head */
+    list->head->prev = tail;
+    tail->prev = NULL;
+    tail->next = list->head;
+    list->head = tail;
+}

+ 93 - 0
ext/hiredis-vip-0.3.0/adlist.h

@@ -0,0 +1,93 @@
+/* adlist.h - A generic doubly linked list implementation
+ *
+ * Copyright (c) 2006-2012, Salvatore Sanfilippo <antirez at gmail dot com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Redis nor the names of its contributors may be used
+ *     to endorse or promote products derived from this software without
+ *     specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __ADLIST_H__
+#define __ADLIST_H__
+
+/* Node, List, and Iterator are the only data structures used currently. */
+
+typedef struct listNode {
+    struct listNode *prev;
+    struct listNode *next;
+    void *value;
+} listNode;
+
+typedef struct listIter {
+    listNode *next;
+    int direction;
+} listIter;
+
+typedef struct hilist {
+    listNode *head;
+    listNode *tail;
+    void *(*dup)(void *ptr);
+    void (*free)(void *ptr);
+    int (*match)(void *ptr, void *key);
+    unsigned long len;
+} hilist;
+
+/* Functions implemented as macros */
+#define listLength(l) ((l)->len)
+#define listFirst(l) ((l)->head)
+#define listLast(l) ((l)->tail)
+#define listPrevNode(n) ((n)->prev)
+#define listNextNode(n) ((n)->next)
+#define listNodeValue(n) ((n)->value)
+
+#define listSetDupMethod(l,m) ((l)->dup = (m))
+#define listSetFreeMethod(l,m) ((l)->free = (m))
+#define listSetMatchMethod(l,m) ((l)->match = (m))
+
+#define listGetDupMethod(l) ((l)->dup)
+#define listGetFree(l) ((l)->free)
+#define listGetMatchMethod(l) ((l)->match)
+
+/* Prototypes */
+hilist *listCreate(void);
+void listRelease(hilist *list);
+hilist *listAddNodeHead(hilist *list, void *value);
+hilist *listAddNodeTail(hilist *list, void *value);
+hilist *listInsertNode(hilist *list, listNode *old_node, void *value, int after);
+void listDelNode(hilist *list, listNode *node);
+listIter *listGetIterator(hilist *list, int direction);
+listNode *listNext(listIter *iter);
+void listReleaseIterator(listIter *iter);
+hilist *listDup(hilist *orig);
+listNode *listSearchKey(hilist *list, void *key);
+listNode *listIndex(hilist *list, long index);
+void listRewind(hilist *list, listIter *li);
+void listRewindTail(hilist *list, listIter *li);
+void listRotate(hilist *list);
+
+/* Directions for iterators */
+#define AL_START_HEAD 0
+#define AL_START_TAIL 1
+
+#endif /* __ADLIST_H__ */

+ 691 - 0
ext/hiredis-vip-0.3.0/async.c

@@ -0,0 +1,691 @@
+/*
+ * Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com>
+ * Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com>
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Redis nor the names of its contributors may be used
+ *     to endorse or promote products derived from this software without
+ *     specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "fmacros.h"
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include "async.h"
+#include "net.h"
+#include "dict.c"
+#include "sds.h"
+
+#define _EL_ADD_READ(ctx) do { \
+        if ((ctx)->ev.addRead) (ctx)->ev.addRead((ctx)->ev.data); \
+    } while(0)
+#define _EL_DEL_READ(ctx) do { \
+        if ((ctx)->ev.delRead) (ctx)->ev.delRead((ctx)->ev.data); \
+    } while(0)
+#define _EL_ADD_WRITE(ctx) do { \
+        if ((ctx)->ev.addWrite) (ctx)->ev.addWrite((ctx)->ev.data); \
+    } while(0)
+#define _EL_DEL_WRITE(ctx) do { \
+        if ((ctx)->ev.delWrite) (ctx)->ev.delWrite((ctx)->ev.data); \
+    } while(0)
+#define _EL_CLEANUP(ctx) do { \
+        if ((ctx)->ev.cleanup) (ctx)->ev.cleanup((ctx)->ev.data); \
+    } while(0);
+
+/* Forward declaration of function in hiredis.c */
+int __redisAppendCommand(redisContext *c, const char *cmd, size_t len);
+
+/* Functions managing dictionary of callbacks for pub/sub. */
+static unsigned int callbackHash(const void *key) {
+    return dictGenHashFunction((const unsigned char *)key,
+                               sdslen((const sds)key));
+}
+
+static void *callbackValDup(void *privdata, const void *src) {
+    ((void) privdata);
+    redisCallback *dup = malloc(sizeof(*dup));
+    memcpy(dup,src,sizeof(*dup));
+    return dup;
+}
+
+static int callbackKeyCompare(void *privdata, const void *key1, const void *key2) {
+    int l1, l2;
+    ((void) privdata);
+
+    l1 = sdslen((const sds)key1);
+    l2 = sdslen((const sds)key2);
+    if (l1 != l2) return 0;
+    return memcmp(key1,key2,l1) == 0;
+}
+
+static void callbackKeyDestructor(void *privdata, void *key) {
+    ((void) privdata);
+    sdsfree((sds)key);
+}
+
+static void callbackValDestructor(void *privdata, void *val) {
+    ((void) privdata);
+    free(val);
+}
+
+static dictType callbackDict = {
+    callbackHash,
+    NULL,
+    callbackValDup,
+    callbackKeyCompare,
+    callbackKeyDestructor,
+    callbackValDestructor
+};
+
+static redisAsyncContext *redisAsyncInitialize(redisContext *c) {
+    redisAsyncContext *ac;
+
+    ac = realloc(c,sizeof(redisAsyncContext));
+    if (ac == NULL)
+        return NULL;
+
+    c = &(ac->c);
+
+    /* The regular connect functions will always set the flag REDIS_CONNECTED.
+     * For the async API, we want to wait until the first write event is
+     * received up before setting this flag, so reset it here. */
+    c->flags &= ~REDIS_CONNECTED;
+
+    ac->err = 0;
+    ac->errstr = NULL;
+    ac->data = NULL;
+    ac->dataHandler = NULL;
+
+    ac->ev.data = NULL;
+    ac->ev.addRead = NULL;
+    ac->ev.delRead = NULL;
+    ac->ev.addWrite = NULL;
+    ac->ev.delWrite = NULL;
+    ac->ev.cleanup = NULL;
+
+    ac->onConnect = NULL;
+    ac->onDisconnect = NULL;
+
+    ac->replies.head = NULL;
+    ac->replies.tail = NULL;
+    ac->sub.invalid.head = NULL;
+    ac->sub.invalid.tail = NULL;
+    ac->sub.channels = dictCreate(&callbackDict,NULL);
+    ac->sub.patterns = dictCreate(&callbackDict,NULL);
+    return ac;
+}
+
+/* We want the error field to be accessible directly instead of requiring
+ * an indirection to the redisContext struct. */
+static void __redisAsyncCopyError(redisAsyncContext *ac) {
+    if (!ac)
+        return;
+
+    redisContext *c = &(ac->c);
+    ac->err = c->err;
+    ac->errstr = c->errstr;
+}
+
+redisAsyncContext *redisAsyncConnect(const char *ip, int port) {
+    redisContext *c;
+    redisAsyncContext *ac;
+
+    c = redisConnectNonBlock(ip,port);
+    if (c == NULL)
+        return NULL;
+
+    ac = redisAsyncInitialize(c);
+    if (ac == NULL) {
+        redisFree(c);
+        return NULL;
+    }
+
+    __redisAsyncCopyError(ac);
+    return ac;
+}
+
+redisAsyncContext *redisAsyncConnectBind(const char *ip, int port,
+                                         const char *source_addr) {
+    redisContext *c = redisConnectBindNonBlock(ip,port,source_addr);
+    redisAsyncContext *ac = redisAsyncInitialize(c);
+    __redisAsyncCopyError(ac);
+    return ac;
+}
+
+redisAsyncContext *redisAsyncConnectBindWithReuse(const char *ip, int port,
+                                                  const char *source_addr) {
+    redisContext *c = redisConnectBindNonBlockWithReuse(ip,port,source_addr);
+    redisAsyncContext *ac = redisAsyncInitialize(c);
+    __redisAsyncCopyError(ac);
+    return ac;
+}
+
+redisAsyncContext *redisAsyncConnectUnix(const char *path) {
+    redisContext *c;
+    redisAsyncContext *ac;
+
+    c = redisConnectUnixNonBlock(path);
+    if (c == NULL)
+        return NULL;
+
+    ac = redisAsyncInitialize(c);
+    if (ac == NULL) {
+        redisFree(c);
+        return NULL;
+    }
+
+    __redisAsyncCopyError(ac);
+    return ac;
+}
+
+int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn) {
+    if (ac->onConnect == NULL) {
+        ac->onConnect = fn;
+
+        /* The common way to detect an established connection is to wait for
+         * the first write event to be fired. This assumes the related event
+         * library functions are already set. */
+        _EL_ADD_WRITE(ac);
+        return REDIS_OK;
+    }
+    return REDIS_ERR;
+}
+
+int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn) {
+    if (ac->onDisconnect == NULL) {
+        ac->onDisconnect = fn;
+        return REDIS_OK;
+    }
+    return REDIS_ERR;
+}
+
+/* Helper functions to push/shift callbacks */
+static int __redisPushCallback(redisCallbackList *list, redisCallback *source) {
+    redisCallback *cb;
+
+    /* Copy callback from stack to heap */
+    cb = malloc(sizeof(*cb));
+    if (cb == NULL)
+        return REDIS_ERR_OOM;
+
+    if (source != NULL) {
+        memcpy(cb,source,sizeof(*cb));
+        cb->next = NULL;
+    }
+
+    /* Store callback in list */
+    if (list->head == NULL)
+        list->head = cb;
+    if (list->tail != NULL)
+        list->tail->next = cb;
+    list->tail = cb;
+    return REDIS_OK;
+}
+
+static int __redisShiftCallback(redisCallbackList *list, redisCallback *target) {
+    redisCallback *cb = list->head;
+    if (cb != NULL) {
+        list->head = cb->next;
+        if (cb == list->tail)
+            list->tail = NULL;
+
+        /* Copy callback from heap to stack */
+        if (target != NULL)
+            memcpy(target,cb,sizeof(*cb));
+        free(cb);
+        return REDIS_OK;
+    }
+    return REDIS_ERR;
+}
+
+static void __redisRunCallback(redisAsyncContext *ac, redisCallback *cb, redisReply *reply) {
+    redisContext *c = &(ac->c);
+    if (cb->fn != NULL) {
+        c->flags |= REDIS_IN_CALLBACK;
+        cb->fn(ac,reply,cb->privdata);
+        c->flags &= ~REDIS_IN_CALLBACK;
+    }
+}
+
+/* Helper function to free the context. */
+static void __redisAsyncFree(redisAsyncContext *ac) {
+    redisContext *c = &(ac->c);
+    redisCallback cb;
+    dictIterator *it;
+    dictEntry *de;
+
+    /* Execute pending callbacks with NULL reply. */
+    while (__redisShiftCallback(&ac->replies,&cb) == REDIS_OK)
+        __redisRunCallback(ac,&cb,NULL);
+
+    /* Execute callbacks for invalid commands */
+    while (__redisShiftCallback(&ac->sub.invalid,&cb) == REDIS_OK)
+        __redisRunCallback(ac,&cb,NULL);
+
+    /* Run subscription callbacks callbacks with NULL reply */
+    it = dictGetIterator(ac->sub.channels);
+    while ((de = dictNext(it)) != NULL)
+        __redisRunCallback(ac,dictGetEntryVal(de),NULL);
+    dictReleaseIterator(it);
+    dictRelease(ac->sub.channels);
+
+    it = dictGetIterator(ac->sub.patterns);
+    while ((de = dictNext(it)) != NULL)
+        __redisRunCallback(ac,dictGetEntryVal(de),NULL);
+    dictReleaseIterator(it);
+    dictRelease(ac->sub.patterns);
+
+    /* Signal event lib to clean up */
+    _EL_CLEANUP(ac);
+
+    /* Execute disconnect callback. When redisAsyncFree() initiated destroying
+     * this context, the status will always be REDIS_OK. */
+    if (ac->onDisconnect && (c->flags & REDIS_CONNECTED)) {
+        if (c->flags & REDIS_FREEING) {
+            ac->onDisconnect(ac,REDIS_OK);
+        } else {
+            ac->onDisconnect(ac,(ac->err == 0) ? REDIS_OK : REDIS_ERR);
+        }
+    }
+
+    if (ac->dataHandler) {
+        ac->dataHandler(ac);
+    }
+
+    /* Cleanup self */
+    redisFree(c);
+}
+
+/* Free the async context. When this function is called from a callback,
+ * control needs to be returned to redisProcessCallbacks() before actual
+ * free'ing. To do so, a flag is set on the context which is picked up by
+ * redisProcessCallbacks(). Otherwise, the context is immediately free'd. */
+void redisAsyncFree(redisAsyncContext *ac) {
+    redisContext *c = &(ac->c);
+    c->flags |= REDIS_FREEING;
+    if (!(c->flags & REDIS_IN_CALLBACK))
+        __redisAsyncFree(ac);
+}
+
+/* Helper function to make the disconnect happen and clean up. */
+static void __redisAsyncDisconnect(redisAsyncContext *ac) {
+    redisContext *c = &(ac->c);
+
+    /* Make sure error is accessible if there is any */
+    __redisAsyncCopyError(ac);
+
+    if (ac->err == 0) {
+        /* For clean disconnects, there should be no pending callbacks. */
+        assert(__redisShiftCallback(&ac->replies,NULL) == REDIS_ERR);
+    } else {
+        /* Disconnection is caused by an error, make sure that pending
+         * callbacks cannot call new commands. */
+        c->flags |= REDIS_DISCONNECTING;
+    }
+
+    /* For non-clean disconnects, __redisAsyncFree() will execute pending
+     * callbacks with a NULL-reply. */
+    __redisAsyncFree(ac);
+}
+
+/* Tries to do a clean disconnect from Redis, meaning it stops new commands
+ * from being issued, but tries to flush the output buffer and execute
+ * callbacks for all remaining replies. When this function is called from a
+ * callback, there might be more replies and we can safely defer disconnecting
+ * to redisProcessCallbacks(). Otherwise, we can only disconnect immediately
+ * when there are no pending callbacks. */
+void redisAsyncDisconnect(redisAsyncContext *ac) {
+    redisContext *c = &(ac->c);
+    c->flags |= REDIS_DISCONNECTING;
+    if (!(c->flags & REDIS_IN_CALLBACK) && ac->replies.head == NULL)
+        __redisAsyncDisconnect(ac);
+}
+
+static int __redisGetSubscribeCallback(redisAsyncContext *ac, redisReply *reply, redisCallback *dstcb) {
+    redisContext *c = &(ac->c);
+    dict *callbacks;
+    dictEntry *de;
+    int pvariant;
+    char *stype;
+    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) {
+        assert(reply->elements >= 2);
+        assert(reply->element[0]->type == REDIS_REPLY_STRING);
+        stype = reply->element[0]->str;
+        pvariant = (tolower(stype[0]) == 'p') ? 1 : 0;
+
+        if (pvariant)
+            callbacks = ac->sub.patterns;
+        else
+            callbacks = ac->sub.channels;
+
+        /* Locate the right callback */
+        assert(reply->element[1]->type == REDIS_REPLY_STRING);
+        sname = sdsnewlen(reply->element[1]->str,reply->element[1]->len);
+        de = dictFind(callbacks,sname);
+        if (de != NULL) {
+            memcpy(dstcb,dictGetEntryVal(de),sizeof(*dstcb));
+
+            /* If this is an unsubscribe message, remove it. */
+            if (strcasecmp(stype+pvariant,"unsubscribe") == 0) {
+                dictDelete(callbacks,sname);
+
+                /* If this was the last unsubscribe message, revert to
+                 * non-subscribe mode. */
+                assert(reply->element[2]->type == REDIS_REPLY_INTEGER);
+                if (reply->element[2]->integer == 0)
+                    c->flags &= ~REDIS_SUBSCRIBED;
+            }
+        }
+        sdsfree(sname);
+    } else {
+        /* Shift callback for invalid commands. */
+        __redisShiftCallback(&ac->sub.invalid,dstcb);
+    }
+    return REDIS_OK;
+}
+
+void redisProcessCallbacks(redisAsyncContext *ac) {
+    redisContext *c = &(ac->c);
+    redisCallback cb = {NULL, NULL, NULL};
+    void *reply = NULL;
+    int status;
+
+    while((status = redisGetReply(c,&reply)) == REDIS_OK) {
+        if (reply == NULL) {
+            /* When the connection is being disconnected and there are
+             * no more replies, this is the cue to really disconnect. */
+            if (c->flags & REDIS_DISCONNECTING && sdslen(c->obuf) == 0) {
+                __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;
+        }
+
+        /* Even if the context is subscribed, pending regular callbacks will
+         * get a reply before pub/sub messages arrive. */
+        if (__redisShiftCallback(&ac->replies,&cb) != REDIS_OK) {
+            /*
+             * A spontaneous reply in a not-subscribed context can be the error
+             * reply that is sent when a new connection exceeds the maximum
+             * number of allowed connections on the server side.
+             *
+             * This is seen as an error instead of a regular reply because the
+             * server closes the connection after sending it.
+             *
+             * To prevent the error from being overwritten by an EOF error the
+             * connection is closed here. See issue #43.
+             *
+             * Another possibility is that the server is loading its dataset.
+             * In this case we also want to close the connection, and have the
+             * user wait until the server is ready to take our request.
+             */
+            if (((redisReply*)reply)->type == REDIS_REPLY_ERROR) {
+                c->err = REDIS_ERR_OTHER;
+                snprintf(c->errstr,sizeof(c->errstr),"%s",((redisReply*)reply)->str);
+                c->reader->fn->freeObject(reply);
+                __redisAsyncDisconnect(ac);
+                return;
+            }
+            /* No more regular callbacks and no errors, the context *must* be subscribed or monitoring. */
+            assert((c->flags & REDIS_SUBSCRIBED || c->flags & REDIS_MONITORING));
+            if(c->flags & REDIS_SUBSCRIBED)
+                __redisGetSubscribeCallback(ac,reply,&cb);
+        }
+
+        if (cb.fn != NULL) {
+            __redisRunCallback(ac,&cb,reply);
+            c->reader->fn->freeObject(reply);
+
+            /* Proceed with free'ing when redisAsyncFree() was called. */
+            if (c->flags & REDIS_FREEING) {
+                __redisAsyncFree(ac);
+                return;
+            }
+        } else {
+            /* No callback for this reply. This can either be a NULL callback,
+             * or there were no callbacks to begin with. Either way, don't
+             * abort with an error, but simply ignore it because the client
+             * doesn't know what the server will spit out over the wire. */
+            c->reader->fn->freeObject(reply);
+        }
+    }
+
+    /* Disconnect when there was an error reading the reply */
+    if (status != REDIS_OK)
+        __redisAsyncDisconnect(ac);
+}
+
+/* Internal helper function to detect socket status the first time a read or
+ * write event fires. When connecting was not succesful, the connect callback
+ * is called with a REDIS_ERR status and the context is free'd. */
+static int __redisAsyncHandleConnect(redisAsyncContext *ac) {
+    redisContext *c = &(ac->c);
+
+    if (redisCheckSocketError(c) == REDIS_ERR) {
+        /* Try again later when connect(2) is still in progress. */
+        if (errno == EINPROGRESS)
+            return REDIS_OK;
+
+        if (ac->onConnect) ac->onConnect(ac,REDIS_ERR);
+        __redisAsyncDisconnect(ac);
+        return REDIS_ERR;
+    }
+
+    /* Mark context as connected. */
+    c->flags |= REDIS_CONNECTED;
+    if (ac->onConnect) ac->onConnect(ac,REDIS_OK);
+    return REDIS_OK;
+}
+
+/* This function should be called when the socket is readable.
+ * It processes all replies that can be read and executes their callbacks.
+ */
+void redisAsyncHandleRead(redisAsyncContext *ac) {
+    redisContext *c = &(ac->c);
+
+    if (!(c->flags & REDIS_CONNECTED)) {
+        /* Abort connect was not successful. */
+        if (__redisAsyncHandleConnect(ac) != REDIS_OK)
+            return;
+        /* Try again later when the context is still not connected. */
+        if (!(c->flags & REDIS_CONNECTED))
+            return;
+    }
+
+    if (redisBufferRead(c) == REDIS_ERR) {
+        __redisAsyncDisconnect(ac);
+    } else {
+        /* Always re-schedule reads */
+        _EL_ADD_READ(ac);
+        redisProcessCallbacks(ac);
+    }
+}
+
+void redisAsyncHandleWrite(redisAsyncContext *ac) {
+    redisContext *c = &(ac->c);
+    int done = 0;
+
+    if (!(c->flags & REDIS_CONNECTED)) {
+        /* Abort connect was not successful. */
+        if (__redisAsyncHandleConnect(ac) != REDIS_OK)
+            return;
+        /* Try again later when the context is still not connected. */
+        if (!(c->flags & REDIS_CONNECTED))
+            return;
+    }
+
+    if (redisBufferWrite(c,&done) == REDIS_ERR) {
+        __redisAsyncDisconnect(ac);
+    } else {
+        /* Continue writing when not done, stop writing otherwise */
+        if (!done)
+            _EL_ADD_WRITE(ac);
+        else
+            _EL_DEL_WRITE(ac);
+
+        /* Always schedule reads after writes */
+        _EL_ADD_READ(ac);
+    }
+}
+
+/* Sets a pointer to the first argument and its length starting at p. Returns
+ * the number of bytes to skip to get to the following argument. */
+static const char *nextArgument(const char *start, const char **str, size_t *len) {
+    const char *p = start;
+    if (p[0] != '$') {
+        p = strchr(p,'$');
+        if (p == NULL) return NULL;
+    }
+
+    *len = (int)strtol(p+1,NULL,10);
+    p = strchr(p,'\r');
+    assert(p);
+    *str = p+2;
+    return p+2+(*len)+2;
+}
+
+/* Helper function for the redisAsyncCommand* family of functions. Writes a
+ * formatted command to the output buffer and registers the provided callback
+ * function with the context. */
+static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *cmd, size_t len) {
+    redisContext *c = &(ac->c);
+    redisCallback cb;
+    int pvariant, hasnext;
+    const char *cstr, *astr;
+    size_t clen, alen;
+    const char *p;
+    sds sname;
+    int ret;
+
+    /* Don't accept new commands when the connection is about to be closed. */
+    if (c->flags & (REDIS_DISCONNECTING | REDIS_FREEING)) return REDIS_ERR;
+
+    /* Setup callback */
+    cb.fn = fn;
+    cb.privdata = privdata;
+
+    /* Find out which command will be appended. */
+    p = nextArgument(cmd,&cstr,&clen);
+    assert(p != NULL);
+    hasnext = (p[0] == '$');
+    pvariant = (tolower(cstr[0]) == 'p') ? 1 : 0;
+    cstr += pvariant;
+    clen -= pvariant;
+
+    if (hasnext && strncasecmp(cstr,"subscribe\r\n",11) == 0) {
+        c->flags |= REDIS_SUBSCRIBED;
+
+        /* Add every channel/pattern to the list of subscription callbacks. */
+        while ((p = nextArgument(p,&astr,&alen)) != NULL) {
+            sname = sdsnewlen(astr,alen);
+            if (pvariant)
+                ret = dictReplace(ac->sub.patterns,sname,&cb);
+            else
+                ret = dictReplace(ac->sub.channels,sname,&cb);
+
+            if (ret == 0) sdsfree(sname);
+        }
+    } else if (strncasecmp(cstr,"unsubscribe\r\n",13) == 0) {
+        /* It is only useful to call (P)UNSUBSCRIBE when the context is
+         * subscribed to one or more channels or patterns. */
+        if (!(c->flags & REDIS_SUBSCRIBED)) return REDIS_ERR;
+
+        /* (P)UNSUBSCRIBE does not have its own response: every channel or
+         * pattern that is unsubscribed will receive a message. This means we
+         * should not append a callback function for this command. */
+     } else if(strncasecmp(cstr,"monitor\r\n",9) == 0) {
+         /* Set monitor flag and push callback */
+         c->flags |= REDIS_MONITORING;
+         __redisPushCallback(&ac->replies,&cb);
+    } 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);
+    }
+
+    __redisAppendCommand(c,cmd,len);
+
+    /* Always schedule a write when the write buffer is non-empty */
+    _EL_ADD_WRITE(ac);
+
+    return REDIS_OK;
+}
+
+int redisvAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, va_list ap) {
+    char *cmd;
+    int len;
+    int status;
+    len = redisvFormatCommand(&cmd,format,ap);
+
+    /* We don't want to pass -1 or -2 to future functions as a length. */
+    if (len < 0)
+        return REDIS_ERR;
+
+    status = __redisAsyncCommand(ac,fn,privdata,cmd,len);
+    free(cmd);
+    return status;
+}
+
+int redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, ...) {
+    va_list ap;
+    int status;
+    va_start(ap,format);
+    status = redisvAsyncCommand(ac,fn,privdata,format,ap);
+    va_end(ap);
+    return status;
+}
+
+int redisAsyncCommandArgv(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen) {
+    sds cmd;
+    int len;
+    int status;
+    len = redisFormatSdsCommandArgv(&cmd,argc,argv,argvlen);
+    status = __redisAsyncCommand(ac,fn,privdata,cmd,len);
+    sdsfree(cmd);
+    return status;
+}
+
+int redisAsyncFormattedCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *cmd, size_t len) {
+    int status = __redisAsyncCommand(ac,fn,privdata,cmd,len);
+    return status;
+}

+ 130 - 0
ext/hiredis-vip-0.3.0/async.h

@@ -0,0 +1,130 @@
+/*
+ * Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com>
+ * Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com>
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Redis nor the names of its contributors may be used
+ *     to endorse or promote products derived from this software without
+ *     specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __HIREDIS_ASYNC_H
+#define __HIREDIS_ASYNC_H
+#include "hiredis.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct redisAsyncContext; /* need forward declaration of redisAsyncContext */
+struct dict; /* dictionary header is included in async.c */
+
+/* Reply callback prototype and container */
+typedef void (redisCallbackFn)(struct redisAsyncContext*, void*, void*);
+typedef struct redisCallback {
+    struct redisCallback *next; /* simple singly linked list */
+    redisCallbackFn *fn;
+    void *privdata;
+} redisCallback;
+
+/* List of callbacks for either regular replies or pub/sub */
+typedef struct redisCallbackList {
+    redisCallback *head, *tail;
+} redisCallbackList;
+
+/* Connection callback prototypes */
+typedef void (redisDisconnectCallback)(const struct redisAsyncContext*, int status);
+typedef void (redisConnectCallback)(const struct redisAsyncContext*, int status);
+
+/* Context for an async connection to Redis */
+typedef struct redisAsyncContext {
+    /* Hold the regular context, so it can be realloc'ed. */
+    redisContext c;
+
+    /* Setup error flags so they can be used directly. */
+    int err;
+    char *errstr;
+
+    /* Not used by hiredis */
+    void *data;
+    void (*dataHandler)(struct redisAsyncContext* ac);
+
+    /* Event library data and hooks */
+    struct {
+        void *data;
+
+        /* Hooks that are called when the library expects to start
+         * reading/writing. These functions should be idempotent. */
+        void (*addRead)(void *privdata);
+        void (*delRead)(void *privdata);
+        void (*addWrite)(void *privdata);
+        void (*delWrite)(void *privdata);
+        void (*cleanup)(void *privdata);
+    } ev;
+
+    /* Called when either the connection is terminated due to an error or per
+     * user request. The status is set accordingly (REDIS_OK, REDIS_ERR). */
+    redisDisconnectCallback *onDisconnect;
+
+    /* Called when the first write event was received. */
+    redisConnectCallback *onConnect;
+
+    /* Regular command callbacks */
+    redisCallbackList replies;
+
+    /* Subscription callbacks */
+    struct {
+        redisCallbackList invalid;
+        struct dict *channels;
+        struct dict *patterns;
+    } sub;
+} redisAsyncContext;
+
+/* Functions that proxy to hiredis */
+redisAsyncContext *redisAsyncConnect(const char *ip, int port);
+redisAsyncContext *redisAsyncConnectBind(const char *ip, int port, const char *source_addr);
+redisAsyncContext *redisAsyncConnectBindWithReuse(const char *ip, int port,
+                                                  const char *source_addr);
+redisAsyncContext *redisAsyncConnectUnix(const char *path);
+int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn);
+int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn);
+void redisAsyncDisconnect(redisAsyncContext *ac);
+void redisAsyncFree(redisAsyncContext *ac);
+
+/* Handle read/write events */
+void redisAsyncHandleRead(redisAsyncContext *ac);
+void redisAsyncHandleWrite(redisAsyncContext *ac);
+
+/* Command functions for an async context. Write the command to the
+ * output buffer and register the provided callback. */
+int redisvAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, va_list ap);
+int redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, ...);
+int redisAsyncCommandArgv(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen);
+int redisAsyncFormattedCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *cmd, size_t len);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif

+ 1700 - 0
ext/hiredis-vip-0.3.0/command.c

@@ -0,0 +1,1700 @@
+#include <ctype.h>
+#include <errno.h>
+
+#include "command.h"
+#include "hiutil.h"
+#include "hiarray.h"
+
+
+static uint64_t cmd_id = 0;          /* command id counter */
+
+
+/*
+ * Return true, if the redis command take no key, otherwise
+ * return false
+ */
+static int
+redis_argz(struct cmd *r)
+{
+    switch (r->type) {
+    case CMD_REQ_REDIS_PING:
+    case CMD_REQ_REDIS_QUIT:
+        return 1;
+
+    default:
+        break;
+    }
+
+    return 0;
+}
+
+/*
+ * Return true, if the redis command accepts no arguments, otherwise
+ * return false
+ */
+static int
+redis_arg0(struct cmd *r)
+{
+    switch (r->type) {
+    case CMD_REQ_REDIS_EXISTS:
+    case CMD_REQ_REDIS_PERSIST:
+    case CMD_REQ_REDIS_PTTL:
+    case CMD_REQ_REDIS_SORT:
+    case CMD_REQ_REDIS_TTL:
+    case CMD_REQ_REDIS_TYPE:
+    case CMD_REQ_REDIS_DUMP:
+
+    case CMD_REQ_REDIS_DECR:
+    case CMD_REQ_REDIS_GET:
+    case CMD_REQ_REDIS_INCR:
+    case CMD_REQ_REDIS_STRLEN:
+
+    case CMD_REQ_REDIS_HGETALL:
+    case CMD_REQ_REDIS_HKEYS:
+    case CMD_REQ_REDIS_HLEN:
+    case CMD_REQ_REDIS_HVALS:
+
+    case CMD_REQ_REDIS_LLEN:
+    case CMD_REQ_REDIS_LPOP:
+    case CMD_REQ_REDIS_RPOP:
+
+    case CMD_REQ_REDIS_SCARD:
+    case CMD_REQ_REDIS_SMEMBERS:
+    case CMD_REQ_REDIS_SPOP:
+
+    case CMD_REQ_REDIS_ZCARD:
+    case CMD_REQ_REDIS_PFCOUNT:
+    case CMD_REQ_REDIS_AUTH:
+        return 1;
+
+    default:
+        break;
+    }
+
+    return 0;
+}
+
+/*
+ * Return true, if the redis command accepts exactly 1 argument, otherwise
+ * return false
+ */
+static int
+redis_arg1(struct cmd *r)
+{
+    switch (r->type) {
+    case CMD_REQ_REDIS_EXPIRE:
+    case CMD_REQ_REDIS_EXPIREAT:
+    case CMD_REQ_REDIS_PEXPIRE:
+    case CMD_REQ_REDIS_PEXPIREAT:
+
+    case CMD_REQ_REDIS_APPEND:
+    case CMD_REQ_REDIS_DECRBY:
+    case CMD_REQ_REDIS_GETBIT:
+    case CMD_REQ_REDIS_GETSET:
+    case CMD_REQ_REDIS_INCRBY:
+    case CMD_REQ_REDIS_INCRBYFLOAT:
+    case CMD_REQ_REDIS_SETNX:
+
+    case CMD_REQ_REDIS_HEXISTS:
+    case CMD_REQ_REDIS_HGET:
+
+    case CMD_REQ_REDIS_LINDEX:
+    case CMD_REQ_REDIS_LPUSHX:
+    case CMD_REQ_REDIS_RPOPLPUSH:
+    case CMD_REQ_REDIS_RPUSHX:
+
+    case CMD_REQ_REDIS_SISMEMBER:
+
+    case CMD_REQ_REDIS_ZRANK:
+    case CMD_REQ_REDIS_ZREVRANK:
+    case CMD_REQ_REDIS_ZSCORE:
+        return 1;
+
+    default:
+        break;
+    }
+
+    return 0;
+}
+
+/*
+ * Return true, if the redis command accepts exactly 2 arguments, otherwise
+ * return false
+ */
+static int
+redis_arg2(struct cmd *r)
+{
+    switch (r->type) {
+    case CMD_REQ_REDIS_GETRANGE:
+    case CMD_REQ_REDIS_PSETEX:
+    case CMD_REQ_REDIS_SETBIT:
+    case CMD_REQ_REDIS_SETEX:
+    case CMD_REQ_REDIS_SETRANGE:
+
+    case CMD_REQ_REDIS_HINCRBY:
+    case CMD_REQ_REDIS_HINCRBYFLOAT:
+    case CMD_REQ_REDIS_HSET:
+    case CMD_REQ_REDIS_HSETNX:
+
+    case CMD_REQ_REDIS_LRANGE:
+    case CMD_REQ_REDIS_LREM:
+    case CMD_REQ_REDIS_LSET:
+    case CMD_REQ_REDIS_LTRIM:
+
+    case CMD_REQ_REDIS_SMOVE:
+
+    case CMD_REQ_REDIS_ZCOUNT:
+    case CMD_REQ_REDIS_ZLEXCOUNT:
+    case CMD_REQ_REDIS_ZINCRBY:
+    case CMD_REQ_REDIS_ZREMRANGEBYLEX:
+    case CMD_REQ_REDIS_ZREMRANGEBYRANK:
+    case CMD_REQ_REDIS_ZREMRANGEBYSCORE:
+
+    case CMD_REQ_REDIS_RESTORE:
+        return 1;
+
+    default:
+        break;
+    }
+
+    return 0;
+}
+
+/*
+ * Return true, if the redis command accepts exactly 3 arguments, otherwise
+ * return false
+ */
+static int
+redis_arg3(struct cmd *r)
+{
+    switch (r->type) {
+    case CMD_REQ_REDIS_LINSERT:
+        return 1;
+
+    default:
+        break;
+    }
+
+    return 0;
+}
+
+/*
+ * Return true, if the redis command accepts 0 or more arguments, otherwise
+ * return false
+ */
+static int
+redis_argn(struct cmd *r)
+{
+    switch (r->type) {
+    case CMD_REQ_REDIS_BITCOUNT:
+
+    case CMD_REQ_REDIS_SET:
+    case CMD_REQ_REDIS_HDEL:
+    case CMD_REQ_REDIS_HMGET:
+    case CMD_REQ_REDIS_HMSET:
+    case CMD_REQ_REDIS_HSCAN:
+
+    case CMD_REQ_REDIS_LPUSH:
+    case CMD_REQ_REDIS_RPUSH:
+
+    case CMD_REQ_REDIS_SADD:
+    case CMD_REQ_REDIS_SDIFF:
+    case CMD_REQ_REDIS_SDIFFSTORE:
+    case CMD_REQ_REDIS_SINTER:
+    case CMD_REQ_REDIS_SINTERSTORE:
+    case CMD_REQ_REDIS_SREM:
+    case CMD_REQ_REDIS_SUNION:
+    case CMD_REQ_REDIS_SUNIONSTORE:
+    case CMD_REQ_REDIS_SRANDMEMBER:
+    case CMD_REQ_REDIS_SSCAN:
+
+    case CMD_REQ_REDIS_PFADD:
+    case CMD_REQ_REDIS_PFMERGE:
+
+    case CMD_REQ_REDIS_ZADD:
+    case CMD_REQ_REDIS_ZINTERSTORE:
+    case CMD_REQ_REDIS_ZRANGE:
+    case CMD_REQ_REDIS_ZRANGEBYSCORE:
+    case CMD_REQ_REDIS_ZREM:
+    case CMD_REQ_REDIS_ZREVRANGE:
+    case CMD_REQ_REDIS_ZRANGEBYLEX:
+    case CMD_REQ_REDIS_ZREVRANGEBYSCORE:
+    case CMD_REQ_REDIS_ZUNIONSTORE:
+    case CMD_REQ_REDIS_ZSCAN:
+        return 1;
+
+    default:
+        break;
+    }
+
+    return 0;
+}
+
+/*
+ * Return true, if the redis command is a vector command accepting one or
+ * more keys, otherwise return false
+ */
+static int
+redis_argx(struct cmd *r)
+{
+    switch (r->type) {
+    case CMD_REQ_REDIS_MGET:
+    case CMD_REQ_REDIS_DEL:
+        return 1;
+
+    default:
+        break;
+    }
+
+    return 0;
+}
+
+/*
+ * Return true, if the redis command is a vector command accepting one or
+ * more key-value pairs, otherwise return false
+ */
+static int
+redis_argkvx(struct cmd *r)
+{
+    switch (r->type) {
+    case CMD_REQ_REDIS_MSET:
+        return 1;
+
+    default:
+        break;
+    }
+
+    return 0;
+}
+
+/*
+ * Return true, if the redis command is either EVAL or EVALSHA. These commands
+ * have a special format with exactly 2 arguments, followed by one or more keys,
+ * followed by zero or more arguments (the documentation online seems to suggest
+ * that at least one argument is required, but that shouldn't be the case).
+ */
+static int
+redis_argeval(struct cmd *r)
+{
+    switch (r->type) {
+    case CMD_REQ_REDIS_EVAL:
+    case CMD_REQ_REDIS_EVALSHA:
+        return 1;
+
+    default:
+        break;
+    }
+
+    return 0;
+}
+
+/*
+ * Reference: http://redis.io/topics/protocol
+ *
+ * Redis >= 1.2 uses the unified protocol to send requests to the Redis
+ * server. In the unified protocol all the arguments sent to the server
+ * are binary safe and every request has the following general form:
+ *
+ *   *<number of arguments> CR LF
+ *   $<number of bytes of argument 1> CR LF
+ *   <argument data> CR LF
+ *   ...
+ *   $<number of bytes of argument N> CR LF
+ *   <argument data> CR LF
+ *
+ * Before the unified request protocol, redis protocol for requests supported
+ * the following commands
+ * 1). Inline commands: simple commands where arguments are just space
+ *     separated strings. No binary safeness is possible.
+ * 2). Bulk commands: bulk commands are exactly like inline commands, but
+ *     the last argument is handled in a special way in order to allow for
+ *     a binary-safe last argument.
+ *
+ * only supports the Redis unified protocol for requests.
+ */
+void
+redis_parse_cmd(struct cmd *r)
+{
+    int len;
+    char *p, *m, *token = NULL;
+    char *cmd_end;
+    char ch;
+    uint32_t rlen = 0;  /* running length in parsing fsa */
+    uint32_t rnarg = 0; /* running # arg used by parsing fsa */
+    enum {
+        SW_START,
+        SW_NARG,
+        SW_NARG_LF,
+        SW_REQ_TYPE_LEN,
+        SW_REQ_TYPE_LEN_LF,
+        SW_REQ_TYPE,
+        SW_REQ_TYPE_LF,
+        SW_KEY_LEN,
+        SW_KEY_LEN_LF,
+        SW_KEY,
+        SW_KEY_LF,
+        SW_ARG1_LEN,
+        SW_ARG1_LEN_LF,
+        SW_ARG1,
+        SW_ARG1_LF,
+        SW_ARG2_LEN,
+        SW_ARG2_LEN_LF,
+        SW_ARG2,
+        SW_ARG2_LF,
+        SW_ARG3_LEN,
+        SW_ARG3_LEN_LF,
+        SW_ARG3,
+        SW_ARG3_LF,
+        SW_ARGN_LEN,
+        SW_ARGN_LEN_LF,
+        SW_ARGN,
+        SW_ARGN_LF,
+        SW_SENTINEL
+    } state;
+
+    state = SW_START;
+    cmd_end = r->cmd + r->clen;
+
+    ASSERT(state >= SW_START && state < SW_SENTINEL);
+    ASSERT(r->cmd != NULL && r->clen > 0);
+
+    for (p = r->cmd; p < cmd_end; p++) {
+        ch = *p;
+
+        switch (state) {
+
+        case SW_START:
+        case SW_NARG:
+            if (token == NULL) {
+                if (ch != '*') {
+                    goto error;
+                }
+                token = p;
+                /* req_start <- p */
+                r->narg_start = p;
+                rnarg = 0;
+                state = SW_NARG;
+            } else if (isdigit(ch)) {
+                rnarg = rnarg * 10 + (uint32_t)(ch - '0');
+            } else if (ch == CR) {
+                if (rnarg == 0) {
+                    goto error;
+                }
+                r->narg = rnarg;
+                r->narg_end = p;
+                token = NULL;
+                state = SW_NARG_LF;
+            } else {
+                goto error;
+            }
+
+            break;
+
+        case SW_NARG_LF:
+            switch (ch) {
+            case LF:
+                state = SW_REQ_TYPE_LEN;
+                break;
+
+            default:
+                goto error;
+            }
+
+            break;
+
+        case SW_REQ_TYPE_LEN:
+            if (token == NULL) {
+                if (ch != '$') {
+                    goto error;
+                }
+                token = p;
+                rlen = 0;
+            } else if (isdigit(ch)) {
+                rlen = rlen * 10 + (uint32_t)(ch - '0');
+            } else if (ch == CR) {
+                if (rlen == 0 || rnarg == 0) {
+                    goto error;
+                }
+                rnarg--;
+                token = NULL;
+                state = SW_REQ_TYPE_LEN_LF;
+            } else {
+                goto error;
+            }
+
+            break;
+
+        case SW_REQ_TYPE_LEN_LF:
+            switch (ch) {
+            case LF:
+                state = SW_REQ_TYPE;
+                break;
+
+            default:
+                goto error;
+            }
+
+            break;
+
+        case SW_REQ_TYPE:
+            if (token == NULL) {
+                token = p;
+            }
+
+            m = token + rlen;
+            if (m >= cmd_end) {
+                //m = cmd_end - 1;
+                //p = m;
+                //break;
+                goto error;
+            }
+
+            if (*m != CR) {
+                goto error;
+            }
+
+            p = m; /* move forward by rlen bytes */
+            rlen = 0;
+            m = token;
+            token = NULL;
+            r->type = CMD_UNKNOWN;
+
+            switch (p - m) {
+
+            case 3:
+                if (str3icmp(m, 'g', 'e', 't')) {
+                    r->type = CMD_REQ_REDIS_GET;
+                    break;
+                }
+
+                if (str3icmp(m, 's', 'e', 't')) {
+                    r->type = CMD_REQ_REDIS_SET;
+                    break;
+                }
+
+                if (str3icmp(m, 't', 't', 'l')) {
+                    r->type = CMD_REQ_REDIS_TTL;
+                    break;
+                }
+
+                if (str3icmp(m, 'd', 'e', 'l')) {
+                    r->type = CMD_REQ_REDIS_DEL;
+                    break;
+                }
+
+                break;
+
+            case 4:
+                if (str4icmp(m, 'p', 't', 't', 'l')) {
+                    r->type = CMD_REQ_REDIS_PTTL;
+                    break;
+                }
+
+                if (str4icmp(m, 'd', 'e', 'c', 'r')) {
+                    r->type = CMD_REQ_REDIS_DECR;
+                    break;
+                }
+
+                if (str4icmp(m, 'd', 'u', 'm', 'p')) {
+                    r->type = CMD_REQ_REDIS_DUMP;
+                    break;
+                }
+
+                if (str4icmp(m, 'h', 'd', 'e', 'l')) {
+                    r->type = CMD_REQ_REDIS_HDEL;
+                    break;
+                }
+
+                if (str4icmp(m, 'h', 'g', 'e', 't')) {
+                    r->type = CMD_REQ_REDIS_HGET;
+                    break;
+                }
+
+                if (str4icmp(m, 'h', 'l', 'e', 'n')) {
+                    r->type = CMD_REQ_REDIS_HLEN;
+                    break;
+                }
+
+                if (str4icmp(m, 'h', 's', 'e', 't')) {
+                    r->type = CMD_REQ_REDIS_HSET;
+                    break;
+                }
+
+                if (str4icmp(m, 'i', 'n', 'c', 'r')) {
+                    r->type = CMD_REQ_REDIS_INCR;
+                    break;
+                }
+
+                if (str4icmp(m, 'l', 'l', 'e', 'n')) {
+                    r->type = CMD_REQ_REDIS_LLEN;
+                    break;
+                }
+
+                if (str4icmp(m, 'l', 'p', 'o', 'p')) {
+                    r->type = CMD_REQ_REDIS_LPOP;
+                    break;
+                }
+
+                if (str4icmp(m, 'l', 'r', 'e', 'm')) {
+                    r->type = CMD_REQ_REDIS_LREM;
+                    break;
+                }
+
+                if (str4icmp(m, 'l', 's', 'e', 't')) {
+                    r->type = CMD_REQ_REDIS_LSET;
+                    break;
+                }
+
+                if (str4icmp(m, 'r', 'p', 'o', 'p')) {
+                    r->type = CMD_REQ_REDIS_RPOP;
+                    break;
+                }
+
+                if (str4icmp(m, 's', 'a', 'd', 'd')) {
+                    r->type = CMD_REQ_REDIS_SADD;
+                    break;
+                }
+
+                if (str4icmp(m, 's', 'p', 'o', 'p')) {
+                    r->type = CMD_REQ_REDIS_SPOP;
+                    break;
+                }
+
+                if (str4icmp(m, 's', 'r', 'e', 'm')) {
+                    r->type = CMD_REQ_REDIS_SREM;
+                    break;
+                }
+
+                if (str4icmp(m, 't', 'y', 'p', 'e')) {
+                    r->type = CMD_REQ_REDIS_TYPE;
+                    break;
+                }
+
+                if (str4icmp(m, 'm', 'g', 'e', 't')) {
+                    r->type = CMD_REQ_REDIS_MGET;
+                    break;
+                }
+                if (str4icmp(m, 'm', 's', 'e', 't')) {
+                    r->type = CMD_REQ_REDIS_MSET;
+                    break;
+                }
+
+                if (str4icmp(m, 'z', 'a', 'd', 'd')) {
+                    r->type = CMD_REQ_REDIS_ZADD;
+                    break;
+                }
+
+                if (str4icmp(m, 'z', 'r', 'e', 'm')) {
+                    r->type = CMD_REQ_REDIS_ZREM;
+                    break;
+                }
+
+                if (str4icmp(m, 'e', 'v', 'a', 'l')) {
+                    r->type = CMD_REQ_REDIS_EVAL;
+                    break;
+                }
+
+                if (str4icmp(m, 's', 'o', 'r', 't')) {
+                    r->type = CMD_REQ_REDIS_SORT;
+                    break;
+                }
+
+                if (str4icmp(m, 'p', 'i', 'n', 'g')) {
+                    r->type = CMD_REQ_REDIS_PING;
+                    r->noforward = 1;
+                    break;
+                }
+
+                if (str4icmp(m, 'q', 'u', 'i', 't')) {
+                    r->type = CMD_REQ_REDIS_QUIT;
+                    r->quit = 1;
+                    break;
+                }
+
+                if (str4icmp(m, 'a', 'u', 't', 'h')) {
+                    r->type = CMD_REQ_REDIS_AUTH;
+                    r->noforward = 1;
+                    break;
+                }
+
+                break;
+
+            case 5:
+                if (str5icmp(m, 'h', 'k', 'e', 'y', 's')) {
+                    r->type = CMD_REQ_REDIS_HKEYS;
+                    break;
+                }
+
+                if (str5icmp(m, 'h', 'm', 'g', 'e', 't')) {
+                    r->type = CMD_REQ_REDIS_HMGET;
+                    break;
+                }
+
+                if (str5icmp(m, 'h', 'm', 's', 'e', 't')) {
+                    r->type = CMD_REQ_REDIS_HMSET;
+                    break;
+                }
+
+                if (str5icmp(m, 'h', 'v', 'a', 'l', 's')) {
+                    r->type = CMD_REQ_REDIS_HVALS;
+                    break;
+                }
+
+                if (str5icmp(m, 'h', 's', 'c', 'a', 'n')) {
+                    r->type = CMD_REQ_REDIS_HSCAN;
+                    break;
+                }
+
+                if (str5icmp(m, 'l', 'p', 'u', 's', 'h')) {
+                    r->type = CMD_REQ_REDIS_LPUSH;
+                    break;
+                }
+
+                if (str5icmp(m, 'l', 't', 'r', 'i', 'm')) {
+                    r->type = CMD_REQ_REDIS_LTRIM;
+                    break;
+                }
+
+                if (str5icmp(m, 'r', 'p', 'u', 's', 'h')) {
+                    r->type = CMD_REQ_REDIS_RPUSH;
+                    break;
+                }
+
+                if (str5icmp(m, 's', 'c', 'a', 'r', 'd')) {
+                    r->type = CMD_REQ_REDIS_SCARD;
+                    break;
+                }
+
+                if (str5icmp(m, 's', 'd', 'i', 'f', 'f')) {
+                    r->type = CMD_REQ_REDIS_SDIFF;
+                    break;
+                }
+
+                if (str5icmp(m, 's', 'e', 't', 'e', 'x')) {
+                    r->type = CMD_REQ_REDIS_SETEX;
+                    break;
+                }
+
+                if (str5icmp(m, 's', 'e', 't', 'n', 'x')) {
+                    r->type = CMD_REQ_REDIS_SETNX;
+                    break;
+                }
+
+                if (str5icmp(m, 's', 'm', 'o', 'v', 'e')) {
+                    r->type = CMD_REQ_REDIS_SMOVE;
+                    break;
+                }
+
+                if (str5icmp(m, 's', 's', 'c', 'a', 'n')) {
+                    r->type = CMD_REQ_REDIS_SSCAN;
+                    break;
+                }
+
+                if (str5icmp(m, 'z', 'c', 'a', 'r', 'd')) {
+                    r->type = CMD_REQ_REDIS_ZCARD;
+                    break;
+                }
+
+                if (str5icmp(m, 'z', 'r', 'a', 'n', 'k')) {
+                    r->type = CMD_REQ_REDIS_ZRANK;
+                    break;
+                }
+
+                if (str5icmp(m, 'z', 's', 'c', 'a', 'n')) {
+                    r->type = CMD_REQ_REDIS_ZSCAN;
+                    break;
+                }
+
+                if (str5icmp(m, 'p', 'f', 'a', 'd', 'd')) {
+                    r->type = CMD_REQ_REDIS_PFADD;
+                    break;
+                }
+
+                break;
+
+            case 6:
+                if (str6icmp(m, 'a', 'p', 'p', 'e', 'n', 'd')) {
+                    r->type = CMD_REQ_REDIS_APPEND;
+                    break;
+                }
+
+                if (str6icmp(m, 'd', 'e', 'c', 'r', 'b', 'y')) {
+                    r->type = CMD_REQ_REDIS_DECRBY;
+                    break;
+                }
+
+                if (str6icmp(m, 'e', 'x', 'i', 's', 't', 's')) {
+                    r->type = CMD_REQ_REDIS_EXISTS;
+                    break;
+                }
+
+                if (str6icmp(m, 'e', 'x', 'p', 'i', 'r', 'e')) {
+                    r->type = CMD_REQ_REDIS_EXPIRE;
+                    break;
+                }
+
+                if (str6icmp(m, 'g', 'e', 't', 'b', 'i', 't')) {
+                    r->type = CMD_REQ_REDIS_GETBIT;
+                    break;
+                }
+
+                if (str6icmp(m, 'g', 'e', 't', 's', 'e', 't')) {
+                    r->type = CMD_REQ_REDIS_GETSET;
+                    break;
+                }
+
+                if (str6icmp(m, 'p', 's', 'e', 't', 'e', 'x')) {
+                    r->type = CMD_REQ_REDIS_PSETEX;
+                    break;
+                }
+
+                if (str6icmp(m, 'h', 's', 'e', 't', 'n', 'x')) {
+                    r->type = CMD_REQ_REDIS_HSETNX;
+                    break;
+                }
+
+                if (str6icmp(m, 'i', 'n', 'c', 'r', 'b', 'y')) {
+                    r->type = CMD_REQ_REDIS_INCRBY;
+                    break;
+                }
+
+                if (str6icmp(m, 'l', 'i', 'n', 'd', 'e', 'x')) {
+                    r->type = CMD_REQ_REDIS_LINDEX;
+                    break;
+                }
+
+                if (str6icmp(m, 'l', 'p', 'u', 's', 'h', 'x')) {
+                    r->type = CMD_REQ_REDIS_LPUSHX;
+                    break;
+                }
+
+                if (str6icmp(m, 'l', 'r', 'a', 'n', 'g', 'e')) {
+                    r->type = CMD_REQ_REDIS_LRANGE;
+                    break;
+                }
+
+                if (str6icmp(m, 'r', 'p', 'u', 's', 'h', 'x')) {
+                    r->type = CMD_REQ_REDIS_RPUSHX;
+                    break;
+                }
+
+                if (str6icmp(m, 's', 'e', 't', 'b', 'i', 't')) {
+                    r->type = CMD_REQ_REDIS_SETBIT;
+                    break;
+                }
+
+                if (str6icmp(m, 's', 'i', 'n', 't', 'e', 'r')) {
+                    r->type = CMD_REQ_REDIS_SINTER;
+                    break;
+                }
+
+                if (str6icmp(m, 's', 't', 'r', 'l', 'e', 'n')) {
+                    r->type = CMD_REQ_REDIS_STRLEN;
+                    break;
+                }
+
+                if (str6icmp(m, 's', 'u', 'n', 'i', 'o', 'n')) {
+                    r->type = CMD_REQ_REDIS_SUNION;
+                    break;
+                }
+
+                if (str6icmp(m, 'z', 'c', 'o', 'u', 'n', 't')) {
+                    r->type = CMD_REQ_REDIS_ZCOUNT;
+                    break;
+                }
+
+                if (str6icmp(m, 'z', 'r', 'a', 'n', 'g', 'e')) {
+                    r->type = CMD_REQ_REDIS_ZRANGE;
+                    break;
+                }
+
+                if (str6icmp(m, 'z', 's', 'c', 'o', 'r', 'e')) {
+                    r->type = CMD_REQ_REDIS_ZSCORE;
+                    break;
+                }
+
+                break;
+
+            case 7:
+                if (str7icmp(m, 'p', 'e', 'r', 's', 'i', 's', 't')) {
+                    r->type = CMD_REQ_REDIS_PERSIST;
+                    break;
+                }
+
+                if (str7icmp(m, 'p', 'e', 'x', 'p', 'i', 'r', 'e')) {
+                    r->type = CMD_REQ_REDIS_PEXPIRE;
+                    break;
+                }
+
+                if (str7icmp(m, 'h', 'e', 'x', 'i', 's', 't', 's')) {
+                    r->type = CMD_REQ_REDIS_HEXISTS;
+                    break;
+                }
+
+                if (str7icmp(m, 'h', 'g', 'e', 't', 'a', 'l', 'l')) {
+                    r->type = CMD_REQ_REDIS_HGETALL;
+                    break;
+                }
+
+                if (str7icmp(m, 'h', 'i', 'n', 'c', 'r', 'b', 'y')) {
+                    r->type = CMD_REQ_REDIS_HINCRBY;
+                    break;
+                }
+
+                if (str7icmp(m, 'l', 'i', 'n', 's', 'e', 'r', 't')) {
+                    r->type = CMD_REQ_REDIS_LINSERT;
+                    break;
+                }
+
+                if (str7icmp(m, 'z', 'i', 'n', 'c', 'r', 'b', 'y')) {
+                    r->type = CMD_REQ_REDIS_ZINCRBY;
+                    break;
+                }
+
+                if (str7icmp(m, 'e', 'v', 'a', 'l', 's', 'h', 'a')) {
+                    r->type = CMD_REQ_REDIS_EVALSHA;
+                    break;
+                }
+
+                if (str7icmp(m, 'r', 'e', 's', 't', 'o', 'r', 'e')) {
+                    r->type = CMD_REQ_REDIS_RESTORE;
+                    break;
+                }
+
+                if (str7icmp(m, 'p', 'f', 'c', 'o', 'u', 'n', 't')) {
+                    r->type = CMD_REQ_REDIS_PFCOUNT;
+                    break;
+                }
+
+                if (str7icmp(m, 'p', 'f', 'm', 'e', 'r', 'g', 'e')) {
+                    r->type = CMD_REQ_REDIS_PFMERGE;
+                    break;
+                }
+
+                break;
+
+            case 8:
+                if (str8icmp(m, 'e', 'x', 'p', 'i', 'r', 'e', 'a', 't')) {
+                    r->type = CMD_REQ_REDIS_EXPIREAT;
+                    break;
+                }
+
+                if (str8icmp(m, 'b', 'i', 't', 'c', 'o', 'u', 'n', 't')) {
+                    r->type = CMD_REQ_REDIS_BITCOUNT;
+                    break;
+                }
+
+                if (str8icmp(m, 'g', 'e', 't', 'r', 'a', 'n', 'g', 'e')) {
+                    r->type = CMD_REQ_REDIS_GETRANGE;
+                    break;
+                }
+
+                if (str8icmp(m, 's', 'e', 't', 'r', 'a', 'n', 'g', 'e')) {
+                    r->type = CMD_REQ_REDIS_SETRANGE;
+                    break;
+                }
+
+                if (str8icmp(m, 's', 'm', 'e', 'm', 'b', 'e', 'r', 's')) {
+                    r->type = CMD_REQ_REDIS_SMEMBERS;
+                    break;
+                }
+
+                if (str8icmp(m, 'z', 'r', 'e', 'v', 'r', 'a', 'n', 'k')) {
+                    r->type = CMD_REQ_REDIS_ZREVRANK;
+                    break;
+                }
+
+                break;
+
+            case 9:
+                if (str9icmp(m, 'p', 'e', 'x', 'p', 'i', 'r', 'e', 'a', 't')) {
+                    r->type = CMD_REQ_REDIS_PEXPIREAT;
+                    break;
+                }
+
+                if (str9icmp(m, 'r', 'p', 'o', 'p', 'l', 'p', 'u', 's', 'h')) {
+                    r->type = CMD_REQ_REDIS_RPOPLPUSH;
+                    break;
+                }
+
+                if (str9icmp(m, 's', 'i', 's', 'm', 'e', 'm', 'b', 'e', 'r')) {
+                    r->type = CMD_REQ_REDIS_SISMEMBER;
+                    break;
+                }
+
+                if (str9icmp(m, 'z', 'r', 'e', 'v', 'r', 'a', 'n', 'g', 'e')) {
+                    r->type = CMD_REQ_REDIS_ZREVRANGE;
+                    break;
+                }
+
+                if (str9icmp(m, 'z', 'l', 'e', 'x', 'c', 'o', 'u', 'n', 't')) {
+                    r->type = CMD_REQ_REDIS_ZLEXCOUNT;
+                    break;
+                }
+
+                break;
+
+            case 10:
+                if (str10icmp(m, 's', 'd', 'i', 'f', 'f', 's', 't', 'o', 'r', 'e')) {
+                    r->type = CMD_REQ_REDIS_SDIFFSTORE;
+                    break;
+                }
+
+            case 11:
+                if (str11icmp(m, 'i', 'n', 'c', 'r', 'b', 'y', 'f', 'l', 'o', 'a', 't')) {
+                    r->type = CMD_REQ_REDIS_INCRBYFLOAT;
+                    break;
+                }
+
+                if (str11icmp(m, 's', 'i', 'n', 't', 'e', 'r', 's', 't', 'o', 'r', 'e')) {
+                    r->type = CMD_REQ_REDIS_SINTERSTORE;
+                    break;
+                }
+
+                if (str11icmp(m, 's', 'r', 'a', 'n', 'd', 'm', 'e', 'm', 'b', 'e', 'r')) {
+                    r->type = CMD_REQ_REDIS_SRANDMEMBER;
+                    break;
+                }
+
+                if (str11icmp(m, 's', 'u', 'n', 'i', 'o', 'n', 's', 't', 'o', 'r', 'e')) {
+                    r->type = CMD_REQ_REDIS_SUNIONSTORE;
+                    break;
+                }
+
+                if (str11icmp(m, 'z', 'i', 'n', 't', 'e', 'r', 's', 't', 'o', 'r', 'e')) {
+                    r->type = CMD_REQ_REDIS_ZINTERSTORE;
+                    break;
+                }
+
+                if (str11icmp(m, 'z', 'u', 'n', 'i', 'o', 'n', 's', 't', 'o', 'r', 'e')) {
+                    r->type = CMD_REQ_REDIS_ZUNIONSTORE;
+                    break;
+                }
+
+                if (str11icmp(m, 'z', 'r', 'a', 'n', 'g', 'e', 'b', 'y', 'l', 'e', 'x')) {
+                    r->type = CMD_REQ_REDIS_ZRANGEBYLEX;
+                    break;
+                }
+
+                break;
+
+            case 12:
+                if (str12icmp(m, 'h', 'i', 'n', 'c', 'r', 'b', 'y', 'f', 'l', 'o', 'a', 't')) {
+                    r->type = CMD_REQ_REDIS_HINCRBYFLOAT;
+                    break;
+                }
+
+
+                break;
+
+            case 13:
+                if (str13icmp(m, 'z', 'r', 'a', 'n', 'g', 'e', 'b', 'y', 's', 'c', 'o', 'r', 'e')) {
+                    r->type = CMD_REQ_REDIS_ZRANGEBYSCORE;
+                    break;
+                }
+
+                break;
+
+            case 14:
+                if (str14icmp(m, 'z', 'r', 'e', 'm', 'r', 'a', 'n', 'g', 'e', 'b', 'y', 'l', 'e', 'x')) {
+                    r->type = CMD_REQ_REDIS_ZREMRANGEBYLEX;
+                    break;
+                }
+
+                break;
+
+            case 15:
+                if (str15icmp(m, 'z', 'r', 'e', 'm', 'r', 'a', 'n', 'g', 'e', 'b', 'y', 'r', 'a', 'n', 'k')) {
+                    r->type = CMD_REQ_REDIS_ZREMRANGEBYRANK;
+                    break;
+                }
+
+                break;
+
+            case 16:
+                if (str16icmp(m, 'z', 'r', 'e', 'm', 'r', 'a', 'n', 'g', 'e', 'b', 'y', 's', 'c', 'o', 'r', 'e')) {
+                    r->type = CMD_REQ_REDIS_ZREMRANGEBYSCORE;
+                    break;
+                }
+
+                if (str16icmp(m, 'z', 'r', 'e', 'v', 'r', 'a', 'n', 'g', 'e', 'b', 'y', 's', 'c', 'o', 'r', 'e')) {
+                    r->type = CMD_REQ_REDIS_ZREVRANGEBYSCORE;
+                    break;
+                }
+
+                break;
+
+            default:
+                break;
+            }
+
+            if (r->type == CMD_UNKNOWN) {
+                goto error;
+            }
+
+            state = SW_REQ_TYPE_LF;
+            break;
+
+        case SW_REQ_TYPE_LF:
+            switch (ch) {
+            case LF:
+                if (redis_argz(r)) {
+                    goto done;
+                } else if (redis_argeval(r)) {
+                    state = SW_ARG1_LEN;
+                } else {
+                    state = SW_KEY_LEN;
+                }
+                break;
+
+            default:
+                goto error;
+            }
+
+            break;
+
+        case SW_KEY_LEN:
+            if (token == NULL) {
+                if (ch != '$') {
+                    goto error;
+                }
+                token = p;
+                rlen = 0;
+            } else if (isdigit(ch)) {
+                rlen = rlen * 10 + (uint32_t)(ch - '0');
+            } else if (ch == CR) {
+                
+                if (rnarg == 0) {
+                    goto error;
+                }
+                rnarg--;
+                token = NULL;
+                state = SW_KEY_LEN_LF;
+            } else {
+                goto error;
+            }
+
+            break;
+
+        case SW_KEY_LEN_LF:
+            switch (ch) {
+            case LF:
+                state = SW_KEY;
+                break;
+
+            default:
+                goto error;
+            }
+
+            break;
+
+        case SW_KEY:
+            if (token == NULL) {
+                token = p;
+            }
+
+            m = token + rlen;
+            if (m >= cmd_end) {
+                //m = b->last - 1;
+                //p = m;
+                //break;
+                goto error;
+            }
+
+            if (*m != CR) {
+                goto error;
+            } else {        /* got a key */
+                struct keypos *kpos;
+
+                p = m;      /* move forward by rlen bytes */
+                rlen = 0;
+                m = token;
+                token = NULL;
+
+                kpos = hiarray_push(r->keys);
+                if (kpos == NULL) {
+                    goto enomem;
+                }
+                kpos->start = m;
+                kpos->end = p;
+                //kpos->v_len = 0;
+
+                state = SW_KEY_LF;
+            }
+
+            break;
+
+        case SW_KEY_LF:
+            switch (ch) {
+            case LF:
+                if (redis_arg0(r)) {
+                    if (rnarg != 0) {
+                        goto error;
+                    }
+                    goto done;
+                } else if (redis_arg1(r)) {
+                    if (rnarg != 1) {
+                        goto error;
+                    }
+                    state = SW_ARG1_LEN;
+                } else if (redis_arg2(r)) {
+                    if (rnarg != 2) {
+                        goto error;
+                    }
+                    state = SW_ARG1_LEN;
+                } else if (redis_arg3(r)) {
+                    if (rnarg != 3) {
+                        goto error;
+                    }
+                    state = SW_ARG1_LEN;
+                } else if (redis_argn(r)) {
+                    if (rnarg == 0) {
+                        goto done;
+                    }
+                    state = SW_ARG1_LEN;
+                } else if (redis_argx(r)) {
+                    if (rnarg == 0) {
+                        goto done;
+                    }
+                    state = SW_KEY_LEN;
+                } else if (redis_argkvx(r)) {
+                    if (rnarg == 0) {
+                        goto done;
+                    }
+                    if (r->narg % 2 == 0) {
+                        goto error;
+                    }
+                    state = SW_ARG1_LEN;
+                } else if (redis_argeval(r)) {
+                    if (rnarg == 0) {
+                        goto done;
+                    }
+                    state = SW_ARGN_LEN;
+                } else {
+                    goto error;
+                }
+
+                break;
+
+            default:
+                goto error;
+            }
+
+            break;
+
+        case SW_ARG1_LEN:
+            if (token == NULL) {
+                if (ch != '$') {
+                    goto error;
+                }
+                rlen = 0;
+                token = p;
+            } else if (isdigit(ch)) {
+                rlen = rlen * 10 + (uint32_t)(ch - '0');
+            } else if (ch == CR) {
+                if ((p - token) <= 1 || rnarg == 0) {
+                    goto error;
+                }
+                rnarg--;
+                token = NULL;
+
+                /*
+                //for mset value length
+                if(redis_argkvx(r))
+                {
+                    struct keypos *kpos;
+                    uint32_t array_len = array_n(r->keys);
+                    if(array_len == 0)
+                    {
+                        goto error;
+                    }
+                    
+                    kpos = array_n(r->keys, array_len-1);
+                    if (kpos == NULL || kpos->v_len != 0) {
+                        goto error;
+                    }
+
+                    kpos->v_len = rlen;
+                }
+                */
+                state = SW_ARG1_LEN_LF;
+            } else {
+                goto error;
+            }
+
+            break;
+
+        case SW_ARG1_LEN_LF:
+            switch (ch) {
+            case LF:
+                state = SW_ARG1;
+                break;
+
+            default:
+                goto error;
+            }
+
+            break;
+
+        case SW_ARG1:
+            m = p + rlen;
+            if (m >= cmd_end) {
+                //rlen -= (uint32_t)(b->last - p);
+                //m = b->last - 1;
+                //p = m;
+                //break;
+                goto error;
+            }
+
+            if (*m != CR) {
+                goto error;
+            }
+
+            p = m; /* move forward by rlen bytes */
+            rlen = 0;
+
+            state = SW_ARG1_LF;
+
+            break;
+
+        case SW_ARG1_LF:
+            switch (ch) {
+            case LF:
+                if (redis_arg1(r)) {
+                    if (rnarg != 0) {
+                        goto error;
+                    }
+                    goto done;
+                } else if (redis_arg2(r)) {
+                    if (rnarg != 1) {
+                        goto error;
+                    }
+                    state = SW_ARG2_LEN;
+                } else if (redis_arg3(r)) {
+                    if (rnarg != 2) {
+                        goto error;
+                    }
+                    state = SW_ARG2_LEN;
+                } else if (redis_argn(r)) {
+                    if (rnarg == 0) {
+                        goto done;
+                    }
+                    state = SW_ARGN_LEN;
+                } else if (redis_argeval(r)) {
+                    if (rnarg < 2) {
+                        goto error;
+                    }
+                    state = SW_ARG2_LEN;
+                } else if (redis_argkvx(r)) {
+                    if (rnarg == 0) {
+                        goto done;
+                    }
+                    state = SW_KEY_LEN;
+                } else {
+                    goto error;
+                }
+
+                break;
+
+            default:
+                goto error;
+            }
+
+            break;
+
+        case SW_ARG2_LEN:
+            if (token == NULL) {
+                if (ch != '$') {
+                    goto error;
+                }
+                rlen = 0;
+                token = p;
+            } else if (isdigit(ch)) {
+                rlen = rlen * 10 + (uint32_t)(ch - '0');
+            } else if (ch == CR) {
+                if ((p - token) <= 1 || rnarg == 0) {
+                    goto error;
+                }
+                rnarg--;
+                token = NULL;
+                state = SW_ARG2_LEN_LF;
+            } else {
+                goto error;
+            }
+
+            break;
+
+        case SW_ARG2_LEN_LF:
+            switch (ch) {
+            case LF:
+                state = SW_ARG2;
+                break;
+
+            default:
+                goto error;
+            }
+
+            break;
+
+        case SW_ARG2:
+            if (token == NULL && redis_argeval(r)) {
+                /*
+                 * For EVAL/EVALSHA, ARG2 represents the # key/arg pairs which must
+                 * be tokenized and stored in contiguous memory.
+                 */
+                token = p;
+            }
+
+            m = p + rlen;
+            if (m >= cmd_end) {
+                //rlen -= (uint32_t)(b->last - p);
+                //m = b->last - 1;
+                //p = m;
+                //break;
+                goto error;
+            }
+
+            if (*m != CR) {
+                goto error;
+            }
+
+            p = m; /* move forward by rlen bytes */
+            rlen = 0;
+
+            if (redis_argeval(r)) {
+                uint32_t nkey;
+                char *chp;
+
+                /*
+                 * For EVAL/EVALSHA, we need to find the integer value of this
+                 * argument. It tells us the number of keys in the script, and
+                 * we need to error out if number of keys is 0. At this point,
+                 * both p and m point to the end of the argument and r->token
+                 * points to the start.
+                 */
+                if (p - token < 1) {
+                    goto error;
+                }
+
+                for (nkey = 0, chp = token; chp < p; chp++) {
+                    if (isdigit(*chp)) {
+                        nkey = nkey * 10 + (uint32_t)(*chp - '0');
+                    } else {
+                        goto error;
+                    }
+                }
+                if (nkey == 0) {
+                    goto error;
+                }
+
+                token = NULL;
+            }
+
+            state = SW_ARG2_LF;
+
+            break;
+
+        case SW_ARG2_LF:
+            switch (ch) {
+            case LF:
+                if (redis_arg2(r)) {
+                    if (rnarg != 0) {
+                        goto error;
+                    }
+                    goto done;
+                } else if (redis_arg3(r)) {
+                    if (rnarg != 1) {
+                        goto error;
+                    }
+                    state = SW_ARG3_LEN;
+                } else if (redis_argn(r)) {
+                    if (rnarg == 0) {
+                        goto done;
+                    }
+                    state = SW_ARGN_LEN;
+                } else if (redis_argeval(r)) {
+                    if (rnarg < 1) {
+                        goto error;
+                    }
+                    state = SW_KEY_LEN;
+                } else {
+                    goto error;
+                }
+
+                break;
+
+            default:
+                goto error;
+            }
+
+            break;
+
+        case SW_ARG3_LEN:
+            if (token == NULL) {
+                if (ch != '$') {
+                    goto error;
+                }
+                rlen = 0;
+                token = p;
+            } else if (isdigit(ch)) {
+                rlen = rlen * 10 + (uint32_t)(ch - '0');
+            } else if (ch == CR) {
+                if ((p - token) <= 1 || rnarg == 0) {
+                    goto error;
+                }
+                rnarg--;
+                token = NULL;
+                state = SW_ARG3_LEN_LF;
+            } else {
+                goto error;
+            }
+
+            break;
+
+        case SW_ARG3_LEN_LF:
+            switch (ch) {
+            case LF:
+                state = SW_ARG3;
+                break;
+
+            default:
+                goto error;
+            }
+
+            break;
+
+        case SW_ARG3:
+            m = p + rlen;
+            if (m >= cmd_end) {
+                //rlen -= (uint32_t)(b->last - p);
+                //m = b->last - 1;
+                //p = m;
+                //break;
+                goto error;
+            }
+
+            if (*m != CR) {
+                goto error;
+            }
+
+            p = m; /* move forward by rlen bytes */
+            rlen = 0;
+            state = SW_ARG3_LF;
+
+            break;
+
+        case SW_ARG3_LF:
+            switch (ch) {
+            case LF:
+                if (redis_arg3(r)) {
+                    if (rnarg != 0) {
+                        goto error;
+                    }
+                    goto done;
+                } else if (redis_argn(r)) {
+                    if (rnarg == 0) {
+                        goto done;
+                    }
+                    state = SW_ARGN_LEN;
+                } else {
+                    goto error;
+                }
+
+                break;
+
+            default:
+                goto error;
+            }
+
+            break;
+
+        case SW_ARGN_LEN:
+            if (token == NULL) {
+                if (ch != '$') {
+                    goto error;
+                }
+                rlen = 0;
+                token = p;
+            } else if (isdigit(ch)) {
+                rlen = rlen * 10 + (uint32_t)(ch - '0');
+            } else if (ch == CR) {
+                if ((p - token) <= 1 || rnarg == 0) {
+                    goto error;
+                }
+                rnarg--;
+                token = NULL;
+                state = SW_ARGN_LEN_LF;
+            } else {
+                goto error;
+            }
+
+            break;
+
+        case SW_ARGN_LEN_LF:
+            switch (ch) {
+            case LF:
+                state = SW_ARGN;
+                break;
+
+            default:
+                goto error;
+            }
+
+            break;
+
+        case SW_ARGN:
+            m = p + rlen;
+            if (m >= cmd_end) {
+                //rlen -= (uint32_t)(b->last - p);
+                //m = b->last - 1;
+                //p = m;
+                //break;
+                goto error;
+            }
+
+            if (*m != CR) {
+                goto error;
+            }
+
+            p = m; /* move forward by rlen bytes */
+            rlen = 0;
+            state = SW_ARGN_LF;
+
+            break;
+
+        case SW_ARGN_LF:
+            switch (ch) {
+            case LF:
+                if (redis_argn(r) || redis_argeval(r)) {
+                    if (rnarg == 0) {
+                        goto done;
+                    }
+                    state = SW_ARGN_LEN;
+                } else {
+                    goto error;
+                }
+
+                break;
+
+            default:
+                goto error;
+            }
+
+            break;
+
+        case SW_SENTINEL:
+        default:
+            NOT_REACHED();
+            break;
+        }
+    }
+
+    ASSERT(p == cmd_end);
+
+    return;
+
+done:
+
+    ASSERT(r->type > CMD_UNKNOWN && r->type < CMD_SENTINEL);
+    
+    r->result = CMD_PARSE_OK;
+
+    return;
+
+enomem:
+    
+    r->result = CMD_PARSE_ENOMEM;
+
+    return;
+
+error:
+    
+    r->result = CMD_PARSE_ERROR;
+    errno = EINVAL;
+    if(r->errstr == NULL){
+        r->errstr = hi_alloc(100*sizeof(*r->errstr));
+    }
+
+    len = _scnprintf(r->errstr, 100, "Parse command error. Cmd type: %d, state: %d, break position: %d.", 
+        r->type, state, (int)(p - r->cmd));
+    r->errstr[len] = '\0';
+}
+
+struct cmd *command_get()
+{
+    struct cmd *command;
+    command = hi_alloc(sizeof(struct cmd));
+    if(command == NULL)
+    {
+        return NULL;
+    }
+        
+    command->id = ++cmd_id;
+    command->result = CMD_PARSE_OK;
+    command->errstr = NULL;
+    command->type = CMD_UNKNOWN;
+    command->cmd = NULL;
+    command->clen = 0;
+    command->keys = NULL;
+    command->narg_start = NULL;
+    command->narg_end = NULL;
+    command->narg = 0;
+    command->quit = 0;
+    command->noforward = 0;
+    command->slot_num = -1;
+    command->frag_seq = NULL;
+    command->reply = NULL;
+    command->sub_commands = NULL;
+
+    command->keys = hiarray_create(1, sizeof(struct keypos));
+    if (command->keys == NULL) 
+    {
+        hi_free(command);
+        return NULL;
+    }
+
+    return command;
+}
+
+void command_destroy(struct cmd *command)
+{
+    if(command == NULL)
+    {
+        return;
+    }
+
+    if(command->cmd != NULL)
+    {
+        free(command->cmd);
+    }
+
+    if(command->errstr != NULL){
+        hi_free(command->errstr);
+    }
+
+    if(command->keys != NULL)
+    {
+        command->keys->nelem = 0;
+        hiarray_destroy(command->keys);
+    }
+
+    if(command->frag_seq != NULL)
+    {
+        hi_free(command->frag_seq);
+        command->frag_seq = NULL;
+    }
+
+    if(command->reply != NULL)
+    {
+        freeReplyObject(command->reply);
+    }
+
+    if(command->sub_commands != NULL)
+    {
+        listRelease(command->sub_commands);
+    }
+    
+    hi_free(command);
+}
+
+

+ 179 - 0
ext/hiredis-vip-0.3.0/command.h

@@ -0,0 +1,179 @@
+#ifndef __COMMAND_H_
+#define __COMMAND_H_
+
+#include <stdint.h>
+
+#include "hiredis.h"
+#include "adlist.h"
+
+typedef enum cmd_parse_result {
+    CMD_PARSE_OK,                         /* parsing ok */
+    CMD_PARSE_ENOMEM,                     /* out of memory */
+    CMD_PARSE_ERROR,                      /* parsing error */
+    CMD_PARSE_REPAIR,                     /* more to parse -> repair parsed & unparsed data */
+    CMD_PARSE_AGAIN,                      /* incomplete -> parse again */
+} cmd_parse_result_t;
+
+#define CMD_TYPE_CODEC(ACTION)                                                                      \
+    ACTION( UNKNOWN )                                                                               \
+    ACTION( REQ_REDIS_DEL )                    /* redis commands - keys */                            \
+    ACTION( REQ_REDIS_EXISTS )                                                                      \
+    ACTION( REQ_REDIS_EXPIRE )                                                                      \
+    ACTION( REQ_REDIS_EXPIREAT )                                                                    \
+    ACTION( REQ_REDIS_PEXPIRE )                                                                     \
+    ACTION( REQ_REDIS_PEXPIREAT )                                                                   \
+    ACTION( REQ_REDIS_PERSIST )                                                                     \
+    ACTION( REQ_REDIS_PTTL )                                                                        \
+    ACTION( REQ_REDIS_SORT )                                                                        \
+    ACTION( REQ_REDIS_TTL )                                                                         \
+    ACTION( REQ_REDIS_TYPE )                                                                        \
+    ACTION( REQ_REDIS_APPEND )                 /* redis requests - string */                             \
+    ACTION( REQ_REDIS_BITCOUNT )                                                                    \
+    ACTION( REQ_REDIS_DECR )                                                                        \
+    ACTION( REQ_REDIS_DECRBY )                                                                      \
+    ACTION( REQ_REDIS_DUMP )                                                                        \
+    ACTION( REQ_REDIS_GET )                                                                         \
+    ACTION( REQ_REDIS_GETBIT )                                                                      \
+    ACTION( REQ_REDIS_GETRANGE )                                                                    \
+    ACTION( REQ_REDIS_GETSET )                                                                      \
+    ACTION( REQ_REDIS_INCR )                                                                        \
+    ACTION( REQ_REDIS_INCRBY )                                                                      \
+    ACTION( REQ_REDIS_INCRBYFLOAT )                                                                 \
+    ACTION( REQ_REDIS_MGET )                                                                        \
+    ACTION( REQ_REDIS_MSET )                                                                        \
+    ACTION( REQ_REDIS_PSETEX )                                                                      \
+    ACTION( REQ_REDIS_RESTORE )                                                                     \
+    ACTION( REQ_REDIS_SET )                                                                         \
+    ACTION( REQ_REDIS_SETBIT )                                                                      \
+    ACTION( REQ_REDIS_SETEX )                                                                       \
+    ACTION( REQ_REDIS_SETNX )                                                                       \
+    ACTION( REQ_REDIS_SETRANGE )                                                                    \
+    ACTION( REQ_REDIS_STRLEN )                                                                      \
+    ACTION( REQ_REDIS_HDEL )                   /* redis requests - hashes */                            \
+    ACTION( REQ_REDIS_HEXISTS )                                                                     \
+    ACTION( REQ_REDIS_HGET )                                                                        \
+    ACTION( REQ_REDIS_HGETALL )                                                                     \
+    ACTION( REQ_REDIS_HINCRBY )                                                                     \
+    ACTION( REQ_REDIS_HINCRBYFLOAT )                                                                \
+    ACTION( REQ_REDIS_HKEYS )                                                                       \
+    ACTION( REQ_REDIS_HLEN )                                                                        \
+    ACTION( REQ_REDIS_HMGET )                                                                       \
+    ACTION( REQ_REDIS_HMSET )                                                                       \
+    ACTION( REQ_REDIS_HSET )                                                                        \
+    ACTION( REQ_REDIS_HSETNX )                                                                      \
+    ACTION( REQ_REDIS_HSCAN)                                                                        \
+    ACTION( REQ_REDIS_HVALS )                                                                       \
+    ACTION( REQ_REDIS_LINDEX )                 /* redis requests - lists */                              \
+    ACTION( REQ_REDIS_LINSERT )                                                                     \
+    ACTION( REQ_REDIS_LLEN )                                                                        \
+    ACTION( REQ_REDIS_LPOP )                                                                        \
+    ACTION( REQ_REDIS_LPUSH )                                                                       \
+    ACTION( REQ_REDIS_LPUSHX )                                                                      \
+    ACTION( REQ_REDIS_LRANGE )                                                                      \
+    ACTION( REQ_REDIS_LREM )                                                                        \
+    ACTION( REQ_REDIS_LSET )                                                                        \
+    ACTION( REQ_REDIS_LTRIM )                                                                       \
+    ACTION( REQ_REDIS_PFADD )                  /* redis requests - hyperloglog */                        \
+    ACTION( REQ_REDIS_PFCOUNT )                                                                     \
+    ACTION( REQ_REDIS_PFMERGE )                                                                     \
+    ACTION( REQ_REDIS_RPOP )                                                                        \
+    ACTION( REQ_REDIS_RPOPLPUSH )                                                                   \
+    ACTION( REQ_REDIS_RPUSH )                                                                       \
+    ACTION( REQ_REDIS_RPUSHX )                                                                      \
+    ACTION( REQ_REDIS_SADD )                   /* redis requests - sets */                              \
+    ACTION( REQ_REDIS_SCARD )                                                                       \
+    ACTION( REQ_REDIS_SDIFF )                                                                       \
+    ACTION( REQ_REDIS_SDIFFSTORE )                                                                  \
+    ACTION( REQ_REDIS_SINTER )                                                                      \
+    ACTION( REQ_REDIS_SINTERSTORE )                                                                 \
+    ACTION( REQ_REDIS_SISMEMBER )                                                                   \
+    ACTION( REQ_REDIS_SMEMBERS )                                                                    \
+    ACTION( REQ_REDIS_SMOVE )                                                                       \
+    ACTION( REQ_REDIS_SPOP )                                                                        \
+    ACTION( REQ_REDIS_SRANDMEMBER )                                                                 \
+    ACTION( REQ_REDIS_SREM )                                                                        \
+    ACTION( REQ_REDIS_SUNION )                                                                      \
+    ACTION( REQ_REDIS_SUNIONSTORE )                                                                 \
+    ACTION( REQ_REDIS_SSCAN)                                                                        \
+    ACTION( REQ_REDIS_ZADD )                   /* redis requests - sorted sets */                        \
+    ACTION( REQ_REDIS_ZCARD )                                                                       \
+    ACTION( REQ_REDIS_ZCOUNT )                                                                      \
+    ACTION( REQ_REDIS_ZINCRBY )                                                                     \
+    ACTION( REQ_REDIS_ZINTERSTORE )                                                                 \
+    ACTION( REQ_REDIS_ZLEXCOUNT )                                                                   \
+    ACTION( REQ_REDIS_ZRANGE )                                                                      \
+    ACTION( REQ_REDIS_ZRANGEBYLEX )                                                                 \
+    ACTION( REQ_REDIS_ZRANGEBYSCORE )                                                               \
+    ACTION( REQ_REDIS_ZRANK )                                                                       \
+    ACTION( REQ_REDIS_ZREM )                                                                        \
+    ACTION( REQ_REDIS_ZREMRANGEBYRANK )                                                             \
+    ACTION( REQ_REDIS_ZREMRANGEBYLEX )                                                              \
+    ACTION( REQ_REDIS_ZREMRANGEBYSCORE )                                                            \
+    ACTION( REQ_REDIS_ZREVRANGE )                                                                   \
+    ACTION( REQ_REDIS_ZREVRANGEBYSCORE )                                                            \
+    ACTION( REQ_REDIS_ZREVRANK )                                                                    \
+    ACTION( REQ_REDIS_ZSCORE )                                                                      \
+    ACTION( REQ_REDIS_ZUNIONSTORE )                                                                 \
+    ACTION( REQ_REDIS_ZSCAN)                                                                        \
+    ACTION( REQ_REDIS_EVAL )                   /* redis requests - eval */                              \
+    ACTION( REQ_REDIS_EVALSHA )                                                                     \
+    ACTION( REQ_REDIS_PING )                   /* redis requests - ping/quit */                         \
+    ACTION( REQ_REDIS_QUIT)                                                                         \
+    ACTION( REQ_REDIS_AUTH)                                                                         \
+    ACTION( RSP_REDIS_STATUS )                 /* redis response */                                   \
+    ACTION( RSP_REDIS_ERROR )                                                                       \
+    ACTION( RSP_REDIS_INTEGER )                                                                     \
+    ACTION( RSP_REDIS_BULK )                                                                        \
+    ACTION( RSP_REDIS_MULTIBULK )                                                                   \
+    ACTION( SENTINEL )                                                                              \
+
+
+#define DEFINE_ACTION(_name) CMD_##_name,
+typedef enum cmd_type {
+    CMD_TYPE_CODEC(DEFINE_ACTION)
+} cmd_type_t;
+#undef DEFINE_ACTION
+
+
+struct keypos {
+    char             *start;        /* key start pos */
+    char             *end;          /* key end pos */
+    uint32_t         remain_len;    /* remain length after keypos->end for more key-value pairs in command, like mset */
+};
+
+struct cmd {
+
+    uint64_t             id;              /* command id */
+    
+    cmd_parse_result_t   result;          /* command parsing result */
+    char                 *errstr;         /* error info when the command parse failed */
+
+    cmd_type_t           type;            /* command type */
+
+    char                 *cmd;
+    uint32_t             clen;            /* command length */
+    
+    struct hiarray       *keys;           /* array of keypos, for req */
+
+    char                 *narg_start;     /* narg start (redis) */
+    char                 *narg_end;       /* narg end (redis) */
+    uint32_t             narg;            /* # arguments (redis) */
+
+    unsigned             quit:1;          /* quit request? */
+    unsigned             noforward:1;     /* not need forward (example: ping) */
+
+    int                  slot_num;        /* this command should send to witch slot? 
+                                                                          * -1:the keys in this command cross different slots*/
+    struct cmd           **frag_seq;      /* sequence of fragment command, map from keys to fragments*/
+
+    redisReply           *reply;
+
+    hilist                 *sub_commands;   /* just for pipeline and multi-key commands */
+};
+
+void redis_parse_cmd(struct cmd *r);
+
+struct cmd *command_get(void);
+void command_destroy(struct cmd *command);
+
+#endif

+ 87 - 0
ext/hiredis-vip-0.3.0/crc16.c

@@ -0,0 +1,87 @@
+/*
+ * Copyright 2001-2010 Georges Menie (www.menie.org)
+ * Copyright 2010-2012 Salvatore Sanfilippo (adapted to Redis coding style)
+ * 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 the University of California, Berkeley 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 REGENTS 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 REGENTS AND 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.
+ */
+
+/* CRC16 implementation according to CCITT standards.
+ *
+ * Note by @antirez: this is actually the XMODEM CRC 16 algorithm, using the
+ * following parameters:
+ *
+ * Name                       : "XMODEM", also known as "ZMODEM", "CRC-16/ACORN"
+ * Width                      : 16 bit
+ * Poly                       : 1021 (That is actually x^16 + x^12 + x^5 + 1)
+ * Initialization             : 0000
+ * Reflect Input byte         : False
+ * Reflect Output CRC         : False
+ * Xor constant to output CRC : 0000
+ * Output for "123456789"     : 31C3
+ */
+#include "hiutil.h"
+
+static const uint16_t crc16tab[256]= {
+    0x0000,0x1021,0x2042,0x3063,0x4084,0x50a5,0x60c6,0x70e7,
+    0x8108,0x9129,0xa14a,0xb16b,0xc18c,0xd1ad,0xe1ce,0xf1ef,
+    0x1231,0x0210,0x3273,0x2252,0x52b5,0x4294,0x72f7,0x62d6,
+    0x9339,0x8318,0xb37b,0xa35a,0xd3bd,0xc39c,0xf3ff,0xe3de,
+    0x2462,0x3443,0x0420,0x1401,0x64e6,0x74c7,0x44a4,0x5485,
+    0xa56a,0xb54b,0x8528,0x9509,0xe5ee,0xf5cf,0xc5ac,0xd58d,
+    0x3653,0x2672,0x1611,0x0630,0x76d7,0x66f6,0x5695,0x46b4,
+    0xb75b,0xa77a,0x9719,0x8738,0xf7df,0xe7fe,0xd79d,0xc7bc,
+    0x48c4,0x58e5,0x6886,0x78a7,0x0840,0x1861,0x2802,0x3823,
+    0xc9cc,0xd9ed,0xe98e,0xf9af,0x8948,0x9969,0xa90a,0xb92b,
+    0x5af5,0x4ad4,0x7ab7,0x6a96,0x1a71,0x0a50,0x3a33,0x2a12,
+    0xdbfd,0xcbdc,0xfbbf,0xeb9e,0x9b79,0x8b58,0xbb3b,0xab1a,
+    0x6ca6,0x7c87,0x4ce4,0x5cc5,0x2c22,0x3c03,0x0c60,0x1c41,
+    0xedae,0xfd8f,0xcdec,0xddcd,0xad2a,0xbd0b,0x8d68,0x9d49,
+    0x7e97,0x6eb6,0x5ed5,0x4ef4,0x3e13,0x2e32,0x1e51,0x0e70,
+    0xff9f,0xefbe,0xdfdd,0xcffc,0xbf1b,0xaf3a,0x9f59,0x8f78,
+    0x9188,0x81a9,0xb1ca,0xa1eb,0xd10c,0xc12d,0xf14e,0xe16f,
+    0x1080,0x00a1,0x30c2,0x20e3,0x5004,0x4025,0x7046,0x6067,
+    0x83b9,0x9398,0xa3fb,0xb3da,0xc33d,0xd31c,0xe37f,0xf35e,
+    0x02b1,0x1290,0x22f3,0x32d2,0x4235,0x5214,0x6277,0x7256,
+    0xb5ea,0xa5cb,0x95a8,0x8589,0xf56e,0xe54f,0xd52c,0xc50d,
+    0x34e2,0x24c3,0x14a0,0x0481,0x7466,0x6447,0x5424,0x4405,
+    0xa7db,0xb7fa,0x8799,0x97b8,0xe75f,0xf77e,0xc71d,0xd73c,
+    0x26d3,0x36f2,0x0691,0x16b0,0x6657,0x7676,0x4615,0x5634,
+    0xd94c,0xc96d,0xf90e,0xe92f,0x99c8,0x89e9,0xb98a,0xa9ab,
+    0x5844,0x4865,0x7806,0x6827,0x18c0,0x08e1,0x3882,0x28a3,
+    0xcb7d,0xdb5c,0xeb3f,0xfb1e,0x8bf9,0x9bd8,0xabbb,0xbb9a,
+    0x4a75,0x5a54,0x6a37,0x7a16,0x0af1,0x1ad0,0x2ab3,0x3a92,
+    0xfd2e,0xed0f,0xdd6c,0xcd4d,0xbdaa,0xad8b,0x9de8,0x8dc9,
+    0x7c26,0x6c07,0x5c64,0x4c45,0x3ca2,0x2c83,0x1ce0,0x0cc1,
+    0xef1f,0xff3e,0xcf5d,0xdf7c,0xaf9b,0xbfba,0x8fd9,0x9ff8,
+    0x6e17,0x7e36,0x4e55,0x5e74,0x2e93,0x3eb2,0x0ed1,0x1ef0
+};
+
+uint16_t crc16(const char *buf, int len) {
+    int counter;
+    uint16_t crc = 0;
+    for (counter = 0; counter < len; counter++)
+            crc = (crc<<8) ^ crc16tab[((crc>>8) ^ *buf++)&0x00FF];
+    return crc;
+}

+ 338 - 0
ext/hiredis-vip-0.3.0/dict.c

@@ -0,0 +1,338 @@
+/* Hash table implementation.
+ *
+ * This file implements in memory hash tables with insert/del/replace/find/
+ * get-random-element operations. Hash tables will auto resize if needed
+ * tables of power of two in size are used, collisions are handled by
+ * chaining. See the source code for more information... :)
+ *
+ * Copyright (c) 2006-2010, Salvatore Sanfilippo <antirez at gmail dot com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Redis nor the names of its contributors may be used
+ *     to endorse or promote products derived from this software without
+ *     specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "fmacros.h"
+#include <stdlib.h>
+#include <assert.h>
+#include <limits.h>
+#include "dict.h"
+
+/* -------------------------- private prototypes ---------------------------- */
+
+static int _dictExpandIfNeeded(dict *ht);
+static unsigned long _dictNextPower(unsigned long size);
+static int _dictKeyIndex(dict *ht, const void *key);
+static int _dictInit(dict *ht, dictType *type, void *privDataPtr);
+
+/* -------------------------- hash functions -------------------------------- */
+
+/* Generic hash function (a popular one from Bernstein).
+ * I tested a few and this was the best. */
+static unsigned int dictGenHashFunction(const unsigned char *buf, int len) {
+    unsigned int hash = 5381;
+
+    while (len--)
+        hash = ((hash << 5) + hash) + (*buf++); /* hash * 33 + c */
+    return hash;
+}
+
+/* ----------------------------- API implementation ------------------------- */
+
+/* Reset an hashtable already initialized with ht_init().
+ * NOTE: This function should only called by ht_destroy(). */
+static void _dictReset(dict *ht) {
+    ht->table = NULL;
+    ht->size = 0;
+    ht->sizemask = 0;
+    ht->used = 0;
+}
+
+/* Create a new hash table */
+static dict *dictCreate(dictType *type, void *privDataPtr) {
+    dict *ht = malloc(sizeof(*ht));
+    _dictInit(ht,type,privDataPtr);
+    return ht;
+}
+
+/* Initialize the hash table */
+static int _dictInit(dict *ht, dictType *type, void *privDataPtr) {
+    _dictReset(ht);
+    ht->type = type;
+    ht->privdata = privDataPtr;
+    return DICT_OK;
+}
+
+/* Expand or create the hashtable */
+static int dictExpand(dict *ht, unsigned long size) {
+    dict n; /* the new hashtable */
+    unsigned long realsize = _dictNextPower(size), i;
+
+    /* the size is invalid if it is smaller than the number of
+     * elements already inside the hashtable */
+    if (ht->used > size)
+        return DICT_ERR;
+
+    _dictInit(&n, ht->type, ht->privdata);
+    n.size = realsize;
+    n.sizemask = realsize-1;
+    n.table = calloc(realsize,sizeof(dictEntry*));
+
+    /* Copy all the elements from the old to the new table:
+     * note that if the old hash table is empty ht->size is zero,
+     * so dictExpand just creates an hash table. */
+    n.used = ht->used;
+    for (i = 0; i < ht->size && ht->used > 0; i++) {
+        dictEntry *he, *nextHe;
+
+        if (ht->table[i] == NULL) continue;
+
+        /* For each hash entry on this slot... */
+        he = ht->table[i];
+        while(he) {
+            unsigned int h;
+
+            nextHe = he->next;
+            /* Get the new element index */
+            h = dictHashKey(ht, he->key) & n.sizemask;
+            he->next = n.table[h];
+            n.table[h] = he;
+            ht->used--;
+            /* Pass to the next element */
+            he = nextHe;
+        }
+    }
+    assert(ht->used == 0);
+    free(ht->table);
+
+    /* Remap the new hashtable in the old */
+    *ht = n;
+    return DICT_OK;
+}
+
+/* Add an element to the target hash table */
+static int dictAdd(dict *ht, void *key, void *val) {
+    int index;
+    dictEntry *entry;
+
+    /* Get the index of the new element, or -1 if
+     * the element already exists. */
+    if ((index = _dictKeyIndex(ht, key)) == -1)
+        return DICT_ERR;
+
+    /* Allocates the memory and stores key */
+    entry = malloc(sizeof(*entry));
+    entry->next = ht->table[index];
+    ht->table[index] = entry;
+
+    /* Set the hash entry fields. */
+    dictSetHashKey(ht, entry, key);
+    dictSetHashVal(ht, entry, val);
+    ht->used++;
+    return DICT_OK;
+}
+
+/* Add an element, discarding the old if the key already exists.
+ * Return 1 if the key was added from scratch, 0 if there was already an
+ * element with such key and dictReplace() just performed a value update
+ * operation. */
+static int dictReplace(dict *ht, void *key, void *val) {
+    dictEntry *entry, auxentry;
+
+    /* Try to add the element. If the key
+     * does not exists dictAdd will suceed. */
+    if (dictAdd(ht, key, val) == DICT_OK)
+        return 1;
+    /* It already exists, get the entry */
+    entry = dictFind(ht, key);
+    /* Free the old value and set the new one */
+    /* Set the new value and free the old one. Note that it is important
+     * to do that in this order, as the value may just be exactly the same
+     * as the previous one. In this context, think to reference counting,
+     * you want to increment (set), and then decrement (free), and not the
+     * reverse. */
+    auxentry = *entry;
+    dictSetHashVal(ht, entry, val);
+    dictFreeEntryVal(ht, &auxentry);
+    return 0;
+}
+
+/* Search and remove an element */
+static int dictDelete(dict *ht, const void *key) {
+    unsigned int h;
+    dictEntry *de, *prevde;
+
+    if (ht->size == 0)
+        return DICT_ERR;
+    h = dictHashKey(ht, key) & ht->sizemask;
+    de = ht->table[h];
+
+    prevde = NULL;
+    while(de) {
+        if (dictCompareHashKeys(ht,key,de->key)) {
+            /* Unlink the element from the list */
+            if (prevde)
+                prevde->next = de->next;
+            else
+                ht->table[h] = de->next;
+
+            dictFreeEntryKey(ht,de);
+            dictFreeEntryVal(ht,de);
+            free(de);
+            ht->used--;
+            return DICT_OK;
+        }
+        prevde = de;
+        de = de->next;
+    }
+    return DICT_ERR; /* not found */
+}
+
+/* Destroy an entire hash table */
+static int _dictClear(dict *ht) {
+    unsigned long i;
+
+    /* Free all the elements */
+    for (i = 0; i < ht->size && ht->used > 0; i++) {
+        dictEntry *he, *nextHe;
+
+        if ((he = ht->table[i]) == NULL) continue;
+        while(he) {
+            nextHe = he->next;
+            dictFreeEntryKey(ht, he);
+            dictFreeEntryVal(ht, he);
+            free(he);
+            ht->used--;
+            he = nextHe;
+        }
+    }
+    /* Free the table and the allocated cache structure */
+    free(ht->table);
+    /* Re-initialize the table */
+    _dictReset(ht);
+    return DICT_OK; /* never fails */
+}
+
+/* Clear & Release the hash table */
+static void dictRelease(dict *ht) {
+    _dictClear(ht);
+    free(ht);
+}
+
+static dictEntry *dictFind(dict *ht, const void *key) {
+    dictEntry *he;
+    unsigned int h;
+
+    if (ht->size == 0) return NULL;
+    h = dictHashKey(ht, key) & ht->sizemask;
+    he = ht->table[h];
+    while(he) {
+        if (dictCompareHashKeys(ht, key, he->key))
+            return he;
+        he = he->next;
+    }
+    return NULL;
+}
+
+static dictIterator *dictGetIterator(dict *ht) {
+    dictIterator *iter = malloc(sizeof(*iter));
+
+    iter->ht = ht;
+    iter->index = -1;
+    iter->entry = NULL;
+    iter->nextEntry = NULL;
+    return iter;
+}
+
+static dictEntry *dictNext(dictIterator *iter) {
+    while (1) {
+        if (iter->entry == NULL) {
+            iter->index++;
+            if (iter->index >=
+                    (signed)iter->ht->size) break;
+            iter->entry = iter->ht->table[iter->index];
+        } else {
+            iter->entry = iter->nextEntry;
+        }
+        if (iter->entry) {
+            /* We need to save the 'next' here, the iterator user
+             * may delete the entry we are returning. */
+            iter->nextEntry = iter->entry->next;
+            return iter->entry;
+        }
+    }
+    return NULL;
+}
+
+static void dictReleaseIterator(dictIterator *iter) {
+    free(iter);
+}
+
+/* ------------------------- private functions ------------------------------ */
+
+/* Expand the hash table if needed */
+static int _dictExpandIfNeeded(dict *ht) {
+    /* If the hash table is empty expand it to the intial size,
+     * if the table is "full" dobule its size. */
+    if (ht->size == 0)
+        return dictExpand(ht, DICT_HT_INITIAL_SIZE);
+    if (ht->used == ht->size)
+        return dictExpand(ht, ht->size*2);
+    return DICT_OK;
+}
+
+/* Our hash table capability is a power of two */
+static unsigned long _dictNextPower(unsigned long size) {
+    unsigned long i = DICT_HT_INITIAL_SIZE;
+
+    if (size >= LONG_MAX) return LONG_MAX;
+    while(1) {
+        if (i >= size)
+            return i;
+        i *= 2;
+    }
+}
+
+/* Returns the index of a free slot that can be populated with
+ * an hash entry for the given 'key'.
+ * If the key already exists, -1 is returned. */
+static int _dictKeyIndex(dict *ht, const void *key) {
+    unsigned int h;
+    dictEntry *he;
+
+    /* Expand the hashtable if needed */
+    if (_dictExpandIfNeeded(ht) == DICT_ERR)
+        return -1;
+    /* Compute the key hash value */
+    h = dictHashKey(ht, key) & ht->sizemask;
+    /* Search if this slot does not already contain the given key */
+    he = ht->table[h];
+    while(he) {
+        if (dictCompareHashKeys(ht, key, he->key))
+            return -1;
+        he = he->next;
+    }
+    return h;
+}
+

+ 126 - 0
ext/hiredis-vip-0.3.0/dict.h

@@ -0,0 +1,126 @@
+/* Hash table implementation.
+ *
+ * This file implements in memory hash tables with insert/del/replace/find/
+ * get-random-element operations. Hash tables will auto resize if needed
+ * tables of power of two in size are used, collisions are handled by
+ * chaining. See the source code for more information... :)
+ *
+ * Copyright (c) 2006-2010, Salvatore Sanfilippo <antirez at gmail dot com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Redis nor the names of its contributors may be used
+ *     to endorse or promote products derived from this software without
+ *     specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __DICT_H
+#define __DICT_H
+
+#define DICT_OK 0
+#define DICT_ERR 1
+
+/* Unused arguments generate annoying warnings... */
+#define DICT_NOTUSED(V) ((void) V)
+
+typedef struct dictEntry {
+    void *key;
+    void *val;
+    struct dictEntry *next;
+} dictEntry;
+
+typedef struct dictType {
+    unsigned int (*hashFunction)(const void *key);
+    void *(*keyDup)(void *privdata, const void *key);
+    void *(*valDup)(void *privdata, const void *obj);
+    int (*keyCompare)(void *privdata, const void *key1, const void *key2);
+    void (*keyDestructor)(void *privdata, void *key);
+    void (*valDestructor)(void *privdata, void *obj);
+} dictType;
+
+typedef struct dict {
+    dictEntry **table;
+    dictType *type;
+    unsigned long size;
+    unsigned long sizemask;
+    unsigned long used;
+    void *privdata;
+} dict;
+
+typedef struct dictIterator {
+    dict *ht;
+    int index;
+    dictEntry *entry, *nextEntry;
+} dictIterator;
+
+/* This is the initial size of every hash table */
+#define DICT_HT_INITIAL_SIZE     4
+
+/* ------------------------------- Macros ------------------------------------*/
+#define dictFreeEntryVal(ht, entry) \
+    if ((ht)->type->valDestructor) \
+        (ht)->type->valDestructor((ht)->privdata, (entry)->val)
+
+#define dictSetHashVal(ht, entry, _val_) do { \
+    if ((ht)->type->valDup) \
+        entry->val = (ht)->type->valDup((ht)->privdata, _val_); \
+    else \
+        entry->val = (_val_); \
+} while(0)
+
+#define dictFreeEntryKey(ht, entry) \
+    if ((ht)->type->keyDestructor) \
+        (ht)->type->keyDestructor((ht)->privdata, (entry)->key)
+
+#define dictSetHashKey(ht, entry, _key_) do { \
+    if ((ht)->type->keyDup) \
+        entry->key = (ht)->type->keyDup((ht)->privdata, _key_); \
+    else \
+        entry->key = (_key_); \
+} while(0)
+
+#define dictCompareHashKeys(ht, key1, key2) \
+    (((ht)->type->keyCompare) ? \
+        (ht)->type->keyCompare((ht)->privdata, key1, key2) : \
+        (key1) == (key2))
+
+#define dictHashKey(ht, key) (ht)->type->hashFunction(key)
+
+#define dictGetEntryKey(he) ((he)->key)
+#define dictGetEntryVal(he) ((he)->val)
+#define dictSlots(ht) ((ht)->size)
+#define dictSize(ht) ((ht)->used)
+
+/* API */
+static unsigned int dictGenHashFunction(const unsigned char *buf, int len);
+static dict *dictCreate(dictType *type, void *privDataPtr);
+static int dictExpand(dict *ht, unsigned long size);
+static int dictAdd(dict *ht, void *key, void *val);
+static int dictReplace(dict *ht, void *key, void *val);
+static int dictDelete(dict *ht, const void *key);
+static void dictRelease(dict *ht);
+static dictEntry * dictFind(dict *ht, const void *key);
+static dictIterator *dictGetIterator(dict *ht);
+static dictEntry *dictNext(dictIterator *iter);
+static void dictReleaseIterator(dictIterator *iter);
+
+#endif /* __DICT_H */

+ 62 - 0
ext/hiredis-vip-0.3.0/examples/example-ae.c

@@ -0,0 +1,62 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+
+#include <hiredis.h>
+#include <async.h>
+#include <adapters/ae.h>
+
+/* Put event loop in the global scope, so it can be explicitly stopped */
+static aeEventLoop *loop;
+
+void getCallback(redisAsyncContext *c, void *r, void *privdata) {
+    redisReply *reply = r;
+    if (reply == NULL) return;
+    printf("argv[%s]: %s\n", (char*)privdata, reply->str);
+
+    /* Disconnect after receiving the reply to GET */
+    redisAsyncDisconnect(c);
+}
+
+void connectCallback(const redisAsyncContext *c, int status) {
+    if (status != REDIS_OK) {
+        printf("Error: %s\n", c->errstr);
+        aeStop(loop);
+        return;
+    }
+
+    printf("Connected...\n");
+}
+
+void disconnectCallback(const redisAsyncContext *c, int status) {
+    if (status != REDIS_OK) {
+        printf("Error: %s\n", c->errstr);
+        aeStop(loop);
+        return;
+    }
+
+    printf("Disconnected...\n");
+    aeStop(loop);
+}
+
+int main (int argc, char **argv) {
+    signal(SIGPIPE, SIG_IGN);
+
+    redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379);
+    if (c->err) {
+        /* Let *c leak for now... */
+        printf("Error: %s\n", c->errstr);
+        return 1;
+    }
+
+    loop = aeCreateEventLoop(64);
+    redisAeAttach(loop, c);
+    redisAsyncSetConnectCallback(c,connectCallback);
+    redisAsyncSetDisconnectCallback(c,disconnectCallback);
+    redisAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1]));
+    redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key");
+    aeMain(loop);
+    return 0;
+}
+

+ 73 - 0
ext/hiredis-vip-0.3.0/examples/example-glib.c

@@ -0,0 +1,73 @@
+#include <stdlib.h>
+
+#include <hiredis.h>
+#include <async.h>
+#include <adapters/glib.h>
+
+static GMainLoop *mainloop;
+
+static void
+connect_cb (const redisAsyncContext *ac G_GNUC_UNUSED,
+            int status)
+{
+    if (status != REDIS_OK) {
+        g_printerr("Failed to connect: %s\n", ac->errstr);
+        g_main_loop_quit(mainloop);
+    } else {
+        g_printerr("Connected...\n");
+    }
+}
+
+static void
+disconnect_cb (const redisAsyncContext *ac G_GNUC_UNUSED,
+               int status)
+{
+    if (status != REDIS_OK) {
+        g_error("Failed to disconnect: %s", ac->errstr);
+    } else {
+        g_printerr("Disconnected...\n");
+        g_main_loop_quit(mainloop);
+    }
+}
+
+static void
+command_cb(redisAsyncContext *ac,
+           gpointer r,
+           gpointer user_data G_GNUC_UNUSED)
+{
+    redisReply *reply = r;
+
+    if (reply) {
+        g_print("REPLY: %s\n", reply->str);
+    }
+
+    redisAsyncDisconnect(ac);
+}
+
+gint
+main (gint argc     G_GNUC_UNUSED,
+      gchar *argv[] G_GNUC_UNUSED)
+{
+    redisAsyncContext *ac;
+    GMainContext *context = NULL;
+    GSource *source;
+
+    ac = redisAsyncConnect("127.0.0.1", 6379);
+    if (ac->err) {
+        g_printerr("%s\n", ac->errstr);
+        exit(EXIT_FAILURE);
+    }
+
+    source = redis_source_new(ac);
+    mainloop = g_main_loop_new(context, FALSE);
+    g_source_attach(source, context);
+
+    redisAsyncSetConnectCallback(ac, connect_cb);
+    redisAsyncSetDisconnectCallback(ac, disconnect_cb);
+    redisAsyncCommand(ac, command_cb, NULL, "SET key 1234");
+    redisAsyncCommand(ac, command_cb, NULL, "GET key");
+
+    g_main_loop_run(mainloop);
+
+    return EXIT_SUCCESS;
+}

+ 52 - 0
ext/hiredis-vip-0.3.0/examples/example-libev.c

@@ -0,0 +1,52 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+
+#include <hiredis.h>
+#include <async.h>
+#include <adapters/libev.h>
+
+void getCallback(redisAsyncContext *c, void *r, void *privdata) {
+    redisReply *reply = r;
+    if (reply == NULL) return;
+    printf("argv[%s]: %s\n", (char*)privdata, reply->str);
+
+    /* Disconnect after receiving the reply to GET */
+    redisAsyncDisconnect(c);
+}
+
+void connectCallback(const redisAsyncContext *c, int status) {
+    if (status != REDIS_OK) {
+        printf("Error: %s\n", c->errstr);
+        return;
+    }
+    printf("Connected...\n");
+}
+
+void disconnectCallback(const redisAsyncContext *c, int status) {
+    if (status != REDIS_OK) {
+        printf("Error: %s\n", c->errstr);
+        return;
+    }
+    printf("Disconnected...\n");
+}
+
+int main (int argc, char **argv) {
+    signal(SIGPIPE, SIG_IGN);
+
+    redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379);
+    if (c->err) {
+        /* Let *c leak for now... */
+        printf("Error: %s\n", c->errstr);
+        return 1;
+    }
+
+    redisLibevAttach(EV_DEFAULT_ c);
+    redisAsyncSetConnectCallback(c,connectCallback);
+    redisAsyncSetDisconnectCallback(c,disconnectCallback);
+    redisAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1]));
+    redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key");
+    ev_loop(EV_DEFAULT_ 0);
+    return 0;
+}

+ 53 - 0
ext/hiredis-vip-0.3.0/examples/example-libevent.c

@@ -0,0 +1,53 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+
+#include <hiredis.h>
+#include <async.h>
+#include <adapters/libevent.h>
+
+void getCallback(redisAsyncContext *c, void *r, void *privdata) {
+    redisReply *reply = r;
+    if (reply == NULL) return;
+    printf("argv[%s]: %s\n", (char*)privdata, reply->str);
+
+    /* Disconnect after receiving the reply to GET */
+    redisAsyncDisconnect(c);
+}
+
+void connectCallback(const redisAsyncContext *c, int status) {
+    if (status != REDIS_OK) {
+        printf("Error: %s\n", c->errstr);
+        return;
+    }
+    printf("Connected...\n");
+}
+
+void disconnectCallback(const redisAsyncContext *c, int status) {
+    if (status != REDIS_OK) {
+        printf("Error: %s\n", c->errstr);
+        return;
+    }
+    printf("Disconnected...\n");
+}
+
+int main (int argc, char **argv) {
+    signal(SIGPIPE, SIG_IGN);
+    struct event_base *base = event_base_new();
+
+    redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379);
+    if (c->err) {
+        /* Let *c leak for now... */
+        printf("Error: %s\n", c->errstr);
+        return 1;
+    }
+
+    redisLibeventAttach(c,base);
+    redisAsyncSetConnectCallback(c,connectCallback);
+    redisAsyncSetDisconnectCallback(c,disconnectCallback);
+    redisAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1]));
+    redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key");
+    event_base_dispatch(base);
+    return 0;
+}

+ 53 - 0
ext/hiredis-vip-0.3.0/examples/example-libuv.c

@@ -0,0 +1,53 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+
+#include <hiredis.h>
+#include <async.h>
+#include <adapters/libuv.h>
+
+void getCallback(redisAsyncContext *c, void *r, void *privdata) {
+    redisReply *reply = r;
+    if (reply == NULL) return;
+    printf("argv[%s]: %s\n", (char*)privdata, reply->str);
+
+    /* Disconnect after receiving the reply to GET */
+    redisAsyncDisconnect(c);
+}
+
+void connectCallback(const redisAsyncContext *c, int status) {
+    if (status != REDIS_OK) {
+        printf("Error: %s\n", c->errstr);
+        return;
+    }
+    printf("Connected...\n");
+}
+
+void disconnectCallback(const redisAsyncContext *c, int status) {
+    if (status != REDIS_OK) {
+        printf("Error: %s\n", c->errstr);
+        return;
+    }
+    printf("Disconnected...\n");
+}
+
+int main (int argc, char **argv) {
+    signal(SIGPIPE, SIG_IGN);
+    uv_loop_t* loop = uv_default_loop();
+
+    redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379);
+    if (c->err) {
+        /* Let *c leak for now... */
+        printf("Error: %s\n", c->errstr);
+        return 1;
+    }
+
+    redisLibuvAttach(c,loop);
+    redisAsyncSetConnectCallback(c,connectCallback);
+    redisAsyncSetDisconnectCallback(c,disconnectCallback);
+    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;
+}

+ 78 - 0
ext/hiredis-vip-0.3.0/examples/example.c

@@ -0,0 +1,78 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <hiredis.h>
+
+int main(int argc, char **argv) {
+    unsigned int j;
+    redisContext *c;
+    redisReply *reply;
+    const char *hostname = (argc > 1) ? argv[1] : "127.0.0.1";
+    int port = (argc > 2) ? atoi(argv[2]) : 6379;
+
+    struct timeval timeout = { 1, 500000 }; // 1.5 seconds
+    c = redisConnectWithTimeout(hostname, port, timeout);
+    if (c == NULL || c->err) {
+        if (c) {
+            printf("Connection error: %s\n", c->errstr);
+            redisFree(c);
+        } else {
+            printf("Connection error: can't allocate redis context\n");
+        }
+        exit(1);
+    }
+
+    /* PING server */
+    reply = redisCommand(c,"PING");
+    printf("PING: %s\n", reply->str);
+    freeReplyObject(reply);
+
+    /* Set a key */
+    reply = redisCommand(c,"SET %s %s", "foo", "hello world");
+    printf("SET: %s\n", reply->str);
+    freeReplyObject(reply);
+
+    /* Set a key using binary safe API */
+    reply = redisCommand(c,"SET %b %b", "bar", (size_t) 3, "hello", (size_t) 5);
+    printf("SET (binary API): %s\n", reply->str);
+    freeReplyObject(reply);
+
+    /* Try a GET and two INCR */
+    reply = redisCommand(c,"GET foo");
+    printf("GET foo: %s\n", reply->str);
+    freeReplyObject(reply);
+
+    reply = redisCommand(c,"INCR counter");
+    printf("INCR counter: %lld\n", reply->integer);
+    freeReplyObject(reply);
+    /* again ... */
+    reply = redisCommand(c,"INCR counter");
+    printf("INCR counter: %lld\n", reply->integer);
+    freeReplyObject(reply);
+
+    /* Create a list of numbers, from 0 to 9 */
+    reply = redisCommand(c,"DEL mylist");
+    freeReplyObject(reply);
+    for (j = 0; j < 10; j++) {
+        char buf[64];
+
+        snprintf(buf,64,"%d",j);
+        reply = redisCommand(c,"LPUSH mylist element-%s", buf);
+        freeReplyObject(reply);
+    }
+
+    /* Let's check what we have inside the list */
+    reply = redisCommand(c,"LRANGE mylist 0 -1");
+    if (reply->type == REDIS_REPLY_ARRAY) {
+        for (j = 0; j < reply->elements; j++) {
+            printf("%u) %s\n", j, reply->element[j]->str);
+        }
+    }
+    freeReplyObject(reply);
+
+    /* Disconnects and frees the context */
+    redisFree(c);
+
+    return 0;
+}

+ 23 - 0
ext/hiredis-vip-0.3.0/fmacros.h

@@ -0,0 +1,23 @@
+#ifndef __HIREDIS_FMACRO_H
+#define __HIREDIS_FMACRO_H
+
+#if defined(__linux__)
+#ifndef _BSD_SOURCE
+#define _BSD_SOURCE
+#endif
+#define _DEFAULT_SOURCE
+#endif
+
+#if defined(__sun__)
+#define _POSIX_C_SOURCE 200112L
+#elif defined(__linux__) || defined(__OpenBSD__) || defined(__NetBSD__)
+#define _XOPEN_SOURCE 600
+#else
+#define _XOPEN_SOURCE
+#endif
+
+#if __APPLE__ && __MACH__
+#define _OSX
+#endif
+
+#endif

+ 188 - 0
ext/hiredis-vip-0.3.0/hiarray.c

@@ -0,0 +1,188 @@
+#include <stdlib.h>
+
+#include "hiutil.h"
+#include "hiarray.h"
+
+struct hiarray *
+hiarray_create(uint32_t n, size_t size)
+{
+    struct hiarray *a;
+
+    ASSERT(n != 0 && size != 0);
+
+    a = hi_alloc(sizeof(*a));
+    if (a == NULL) {
+        return NULL;
+    }
+
+    a->elem = hi_alloc(n * size);
+    if (a->elem == NULL) {
+        hi_free(a);
+        return NULL;
+    }
+
+    a->nelem = 0;
+    a->size = size;
+    a->nalloc = n;
+
+    return a;
+}
+
+void
+hiarray_destroy(struct hiarray *a)
+{
+    hiarray_deinit(a);
+    hi_free(a);
+}
+
+int
+hiarray_init(struct hiarray *a, uint32_t n, size_t size)
+{
+    ASSERT(n != 0 && size != 0);
+
+    a->elem = hi_alloc(n * size);
+    if (a->elem == NULL) {
+        return HI_ENOMEM;
+    }
+
+    a->nelem = 0;
+    a->size = size;
+    a->nalloc = n;
+
+    return HI_OK;
+}
+
+void
+hiarray_deinit(struct hiarray *a)
+{
+    ASSERT(a->nelem == 0);
+
+    if (a->elem != NULL) {
+        hi_free(a->elem);
+    }
+}
+
+uint32_t
+hiarray_idx(struct hiarray *a, void *elem)
+{
+    uint8_t *p, *q;
+    uint32_t off, idx;
+
+    ASSERT(elem >= a->elem);
+
+    p = a->elem;
+    q = elem;
+    off = (uint32_t)(q - p);
+
+    ASSERT(off % (uint32_t)a->size == 0);
+
+    idx = off / (uint32_t)a->size;
+
+    return idx;
+}
+
+void *
+hiarray_push(struct hiarray *a)
+{
+    void *elem, *new;
+    size_t size;
+
+    if (a->nelem == a->nalloc) {
+
+        /* the array is full; allocate new array */
+        size = a->size * a->nalloc;
+        new = hi_realloc(a->elem, 2 * size);
+        if (new == NULL) {
+            return NULL;
+        }
+
+        a->elem = new;
+        a->nalloc *= 2;
+    }
+
+    elem = (uint8_t *)a->elem + a->size * a->nelem;
+    a->nelem++;
+
+    return elem;
+}
+
+void *
+hiarray_pop(struct hiarray *a)
+{
+    void *elem;
+
+    ASSERT(a->nelem != 0);
+
+    a->nelem--;
+    elem = (uint8_t *)a->elem + a->size * a->nelem;
+
+    return elem;
+}
+
+void *
+hiarray_get(struct hiarray *a, uint32_t idx)
+{
+    void *elem;
+
+    ASSERT(a->nelem != 0);
+    ASSERT(idx < a->nelem);
+
+    elem = (uint8_t *)a->elem + (a->size * idx);
+
+    return elem;
+}
+
+void *
+hiarray_top(struct hiarray *a)
+{
+    ASSERT(a->nelem != 0);
+
+    return hiarray_get(a, a->nelem - 1);
+}
+
+void
+hiarray_swap(struct hiarray *a, struct hiarray *b)
+{
+    struct hiarray tmp;
+
+    tmp = *a;
+    *a = *b;
+    *b = tmp;
+}
+
+/*
+ * Sort nelem elements of the array in ascending order based on the
+ * compare comparator.
+ */
+void
+hiarray_sort(struct hiarray *a, hiarray_compare_t compare)
+{
+    ASSERT(a->nelem != 0);
+
+    qsort(a->elem, a->nelem, a->size, compare);
+}
+
+/*
+ * Calls the func once for each element in the array as long as func returns
+ * success. On failure short-circuits and returns the error status.
+ */
+int
+hiarray_each(struct hiarray *a, hiarray_each_t func, void *data)
+{
+    uint32_t i, nelem;
+
+    ASSERT(array_n(a) != 0);
+    ASSERT(func != NULL);
+
+    for (i = 0, nelem = hiarray_n(a); i < nelem; i++) {
+        void *elem = hiarray_get(a, i);
+        rstatus_t status;
+
+        status = func(elem, data);
+        if (status != HI_OK) {
+            return status;
+        }
+    }
+
+    return HI_OK;
+}

+ 56 - 0
ext/hiredis-vip-0.3.0/hiarray.h

@@ -0,0 +1,56 @@
+#ifndef __HIARRAY_H_
+#define __HIARRAY_H_
+
+#include <stdio.h>
+
+typedef int (*hiarray_compare_t)(const void *, const void *);
+typedef int (*hiarray_each_t)(void *, void *);
+
+struct hiarray {
+    uint32_t nelem;  /* # element */
+    void     *elem;  /* element */
+    size_t   size;   /* element size */
+    uint32_t nalloc; /* # allocated element */
+};
+
+#define null_hiarray { 0, NULL, 0, 0 }
+
+static inline void
+hiarray_null(struct hiarray *a)
+{
+    a->nelem = 0;
+    a->elem = NULL;
+    a->size = 0;
+    a->nalloc = 0;
+}
+
+static inline void
+hiarray_set(struct hiarray *a, void *elem, size_t size, uint32_t nalloc)
+{
+    a->nelem = 0;
+    a->elem = elem;
+    a->size = size;
+    a->nalloc = nalloc;
+}
+
+static inline uint32_t
+hiarray_n(const struct hiarray *a)
+{
+    return a->nelem;
+}
+
+struct hiarray *hiarray_create(uint32_t n, size_t size);
+void hiarray_destroy(struct hiarray *a);
+int hiarray_init(struct hiarray *a, uint32_t n, size_t size);
+void hiarray_deinit(struct hiarray *a);
+
+uint32_t hiarray_idx(struct hiarray *a, void *elem);
+void *hiarray_push(struct hiarray *a);
+void *hiarray_pop(struct hiarray *a);
+void *hiarray_get(struct hiarray *a, uint32_t idx);
+void *hiarray_top(struct hiarray *a);
+void hiarray_swap(struct hiarray *a, struct hiarray *b);
+void hiarray_sort(struct hiarray *a, hiarray_compare_t compare);
+int hiarray_each(struct hiarray *a, hiarray_each_t func, void *data);
+
+#endif

+ 4747 - 0
ext/hiredis-vip-0.3.0/hircluster.c

@@ -0,0 +1,4747 @@
+
+#include "fmacros.h"
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <ctype.h>
+
+#include "hircluster.h"
+#include "hiutil.h"
+#include "adlist.h"
+#include "hiarray.h"
+#include "command.h"
+#include "dict.c"
+
+#define REDIS_COMMAND_CLUSTER_NODES "CLUSTER NODES"
+#define REDIS_COMMAND_CLUSTER_SLOTS "CLUSTER SLOTS"
+
+#define REDIS_COMMAND_ASKING "ASKING"
+#define REDIS_COMMAND_PING "PING"
+
+#define REDIS_PROTOCOL_ASKING "*1\r\n$6\r\nASKING\r\n"
+
+#define IP_PORT_SEPARATOR ":"
+
+#define CLUSTER_ADDRESS_SEPARATOR ","
+
+#define CLUSTER_DEFAULT_MAX_REDIRECT_COUNT 5
+
+typedef struct cluster_async_data
+{
+    redisClusterAsyncContext *acc;
+    struct cmd *command;
+    redisClusterCallbackFn *callback;
+    int retry_count;
+    void *privdata;
+}cluster_async_data;
+
+typedef enum CLUSTER_ERR_TYPE{
+    CLUSTER_NOT_ERR = 0,
+    CLUSTER_ERR_MOVED,
+    CLUSTER_ERR_ASK,
+    CLUSTER_ERR_TRYAGAIN,
+    CLUSTER_ERR_CROSSSLOT,
+    CLUSTER_ERR_CLUSTERDOWN,
+    CLUSTER_ERR_SENTINEL
+}CLUSTER_ERR_TYPE;
+
+static void cluster_node_deinit(cluster_node *node);
+static void cluster_slot_destroy(cluster_slot *slot);
+static void cluster_open_slot_destroy(copen_slot *oslot);
+
+void listClusterNodeDestructor(void *val)
+{
+    cluster_node_deinit(val);
+
+    hi_free(val);
+}
+
+void listClusterSlotDestructor(void *val)
+{
+    cluster_slot_destroy(val);
+}
+
+unsigned int dictSdsHash(const void *key) {
+    return dictGenHashFunction((unsigned char*)key, sdslen((char*)key));
+}
+
+int dictSdsKeyCompare(void *privdata, const void *key1,
+        const void *key2)
+{
+    int l1,l2;
+    DICT_NOTUSED(privdata);
+
+    l1 = sdslen((sds)key1);
+    l2 = sdslen((sds)key2);
+    if (l1 != l2) return 0;
+    return memcmp(key1, key2, l1) == 0;
+}
+
+void dictSdsDestructor(void *privdata, void *val)
+{
+    DICT_NOTUSED(privdata);
+
+    sdsfree(val);
+}
+
+void dictClusterNodeDestructor(void *privdata, void *val)
+{
+    DICT_NOTUSED(privdata);
+
+    cluster_node_deinit(val);
+
+    hi_free(val);
+}
+
+/* Cluster nodes hash table, mapping nodes 
+ * name(437c719f50dc9d0745032f3b280ce7ecc40792ac)  
+ * or addresses(1.2.3.4:6379) to clusterNode structures.
+ * Those nodes need destroy.
+ */
+dictType clusterNodesDictType = {
+    dictSdsHash,                /* hash function */
+    NULL,                       /* key dup */
+    NULL,                       /* val dup */
+    dictSdsKeyCompare,          /* key compare */
+    dictSdsDestructor,          /* key destructor */
+    dictClusterNodeDestructor   /* val destructor */
+};
+
+/* Cluster nodes hash table, mapping nodes 
+ * name(437c719f50dc9d0745032f3b280ce7ecc40792ac)  
+ * or addresses(1.2.3.4:6379) to clusterNode structures.
+ * Those nodes do not need destroy.
+ */
+dictType clusterNodesRefDictType = {
+    dictSdsHash,                /* hash function */
+    NULL,                       /* key dup */
+    NULL,                       /* val dup */
+    dictSdsKeyCompare,          /* key compare */
+    dictSdsDestructor,          /* key destructor */
+    NULL                        /* val destructor */
+};
+
+
+void listCommandFree(void *command)
+{
+    struct cmd *cmd = command;
+    command_destroy(cmd);
+}
+
+/* Defined in hiredis.c */
+void __redisSetError(redisContext *c, int type, const char *str);
+
+/* Forward declaration of function in hiredis.c */
+int __redisAppendCommand(redisContext *c, const char *cmd, size_t len);
+
+/* Helper function for the redisClusterCommand* family of functions.
+ *
+ * Write a formatted command to the output buffer. If the given context is
+ * blocking, immediately read the reply into the "reply" pointer. When the
+ * context is non-blocking, the "reply" pointer will not be used and the
+ * command is simply appended to the write buffer.
+ *
+ * Returns the reply when a reply was succesfully retrieved. Returns NULL
+ * otherwise. When NULL is returned in a blocking context, the error field
+ * in the context will be set.
+ */
+static void *__redisBlockForReply(redisContext *c) {
+    void *reply;
+
+    if (c->flags & REDIS_BLOCK) {
+        if (redisGetReply(c,&reply) != REDIS_OK)
+            return NULL;
+        return reply;
+    }
+    return NULL;
+}
+
+
+/* -----------------------------------------------------------------------------
+ * Key space handling
+ * -------------------------------------------------------------------------- */
+
+/* We have 16384 hash slots. The hash slot of a given key is obtained
+ * as the least significant 14 bits of the crc16 of the key.
+ *
+ * However if the key contains the {...} pattern, only the part between
+ * { and } is hashed. This may be useful in the future to force certain
+ * keys to be in the same node (assuming no resharding is in progress). */
+static unsigned int keyHashSlot(char *key, int keylen) {
+    int s, e; /* start-end indexes of { and } */
+
+    for (s = 0; s < keylen; s++)
+        if (key[s] == '{') break;
+
+    /* No '{' ? Hash the whole key. This is the base case. */
+    if (s == keylen) return crc16(key,keylen) & 0x3FFF;
+
+    /* '{' found? Check if we have the corresponding '}'. */
+    for (e = s+1; e < keylen; e++)
+        if (key[e] == '}') break;
+
+    /* No '}' or nothing betweeen {} ? Hash the whole key. */
+    if (e == keylen || e == s+1) return crc16(key,keylen) & 0x3FFF;
+
+    /* If we are here there is both a { and a } on its right. Hash
+     * what is in the middle between { and }. */
+    return crc16(key+s+1,e-s-1) & 0x3FFF;
+}
+
+static void __redisClusterSetError(redisClusterContext *cc, int type, const char *str) {
+    size_t len;
+
+    if(cc == NULL){
+        return;
+    }
+
+    cc->err = type;
+    if (str != NULL) {
+        len = strlen(str);
+        len = len < (sizeof(cc->errstr)-1) ? len : (sizeof(cc->errstr)-1);
+        memcpy(cc->errstr,str,len);
+        cc->errstr[len] = '\0';
+    } else {
+        /* Only REDIS_ERR_IO may lack a description! */
+        assert(type == REDIS_ERR_IO);
+        __redis_strerror_r(errno, cc->errstr, sizeof(cc->errstr));
+    }
+}
+
+static int cluster_reply_error_type(redisReply *reply)
+{
+
+    if(reply == NULL)
+    {
+        return REDIS_ERR;
+    }
+
+    if(reply->type == REDIS_REPLY_ERROR)
+    {
+        if((int)strlen(REDIS_ERROR_MOVED) < reply->len && 
+            strncmp(reply->str, REDIS_ERROR_MOVED, strlen(REDIS_ERROR_MOVED)) == 0)
+        {
+            return CLUSTER_ERR_MOVED;
+        }
+        else if((int)strlen(REDIS_ERROR_ASK) < reply->len && 
+            strncmp(reply->str, REDIS_ERROR_ASK, strlen(REDIS_ERROR_ASK)) == 0)
+        {
+            return CLUSTER_ERR_ASK;
+        }
+        else if((int)strlen(REDIS_ERROR_TRYAGAIN) < reply->len && 
+            strncmp(reply->str, REDIS_ERROR_TRYAGAIN, strlen(REDIS_ERROR_TRYAGAIN)) == 0)
+        {
+            return CLUSTER_ERR_TRYAGAIN;
+        }
+        else if((int)strlen(REDIS_ERROR_CROSSSLOT) < reply->len && 
+            strncmp(reply->str, REDIS_ERROR_CROSSSLOT, strlen(REDIS_ERROR_CROSSSLOT)) == 0)
+        {
+            return CLUSTER_ERR_CROSSSLOT;
+        }
+        else if((int)strlen(REDIS_ERROR_CLUSTERDOWN) < reply->len && 
+            strncmp(reply->str, REDIS_ERROR_CLUSTERDOWN, strlen(REDIS_ERROR_CLUSTERDOWN)) == 0)
+        {
+            return CLUSTER_ERR_CLUSTERDOWN;
+        }
+        else
+        {
+            return CLUSTER_ERR_SENTINEL;
+        }
+    }
+
+    return CLUSTER_NOT_ERR;
+}
+
+static int cluster_node_init(cluster_node *node)
+{
+    if(node == NULL){
+        return REDIS_ERR;
+    }
+    
+    node->name = NULL;
+    node->addr = NULL;
+    node->host = NULL;
+    node->port = 0;
+    node->role = REDIS_ROLE_NULL;
+    node->myself = 0;
+    node->slaves = NULL;
+    node->con = NULL;
+    node->acon = NULL;
+    node->slots = NULL;
+    node->failure_count = 0;
+    node->data = NULL;
+    node->migrating = NULL;
+    node->importing = NULL;
+    
+    return REDIS_OK;
+}
+
+static void cluster_node_deinit(cluster_node *node)
+{   
+    copen_slot **oslot;
+    
+    if(node == NULL)
+    {
+        return;
+    }
+
+    sdsfree(node->name);
+    sdsfree(node->addr);
+    sdsfree(node->host);
+    node->port = 0;
+    node->role = REDIS_ROLE_NULL;
+    node->myself = 0;
+
+    if(node->con != NULL)
+    {
+        redisFree(node->con);
+    }
+
+    if(node->acon != NULL)
+    {
+        redisAsyncFree(node->acon);
+    }
+
+    if(node->slots != NULL)
+    {
+        listRelease(node->slots);
+    }
+
+    if(node->slaves != NULL)
+    {
+        listRelease(node->slaves);
+    }
+
+    if(node->migrating)
+    {
+        while(hiarray_n(node->migrating))
+        {
+            oslot = hiarray_pop(node->migrating);
+            cluster_open_slot_destroy(*oslot);
+        }
+        
+        hiarray_destroy(node->migrating);
+        node->migrating = NULL;
+    }
+
+    if(node->importing)
+    {
+        while(hiarray_n(node->importing))
+        {
+            oslot = hiarray_pop(node->importing);
+            cluster_open_slot_destroy(*oslot);
+        }
+        
+        hiarray_destroy(node->importing);
+        node->importing = NULL;
+    }
+}
+
+static int cluster_slot_init(cluster_slot *slot, cluster_node *node)
+{
+    slot->start = 0;
+    slot->end = 0;
+    slot->node = node;
+    
+    return REDIS_OK;
+}
+
+static cluster_slot *cluster_slot_create(cluster_node *node)
+{
+    cluster_slot *slot;
+
+    slot = hi_alloc(sizeof(*slot));
+    if(slot == NULL){
+        return NULL;
+    }
+
+    cluster_slot_init(slot, node);
+
+    if(node != NULL){
+        ASSERT(node->role == REDIS_ROLE_MASTER);
+        if(node->slots == NULL){
+            node->slots = listCreate();
+            if(node->slots == NULL)
+            {
+                cluster_slot_destroy(slot);
+                return NULL;
+            }
+
+            node->slots->free = listClusterSlotDestructor;
+        }
+        
+        listAddNodeTail(node->slots, slot);
+    }
+    
+    return slot;
+}
+
+static int cluster_slot_ref_node(cluster_slot * slot, cluster_node *node)
+{
+    if(slot == NULL || node == NULL){
+        return REDIS_ERR;
+    }
+
+    
+    if(node->role != REDIS_ROLE_MASTER){
+        return REDIS_ERR;
+    }
+    
+    if(node->slots == NULL){
+        node->slots = listCreate();
+        if(node->slots == NULL)
+        {
+            return REDIS_ERR;
+        }
+
+        node->slots->free = listClusterSlotDestructor;
+    }
+    
+    listAddNodeTail(node->slots, slot);
+    slot->node = node;
+    
+    return REDIS_OK;
+}
+
+static void cluster_slot_destroy(cluster_slot *slot)
+{
+    slot->start = 0;
+    slot->end = 0;
+    slot->node = NULL;
+    
+    hi_free(slot);
+}
+
+static copen_slot *cluster_open_slot_create(uint32_t slot_num, int migrate, 
+    sds remote_name, cluster_node *node)
+{
+    copen_slot *oslot;
+
+    oslot = hi_alloc(sizeof(*oslot));
+    if(oslot == NULL){
+        return NULL;
+    }
+
+    oslot->slot_num = 0;
+    oslot->migrate = 0;
+    oslot->node = NULL;
+    oslot->remote_name = NULL;
+
+    oslot->slot_num = slot_num;
+    oslot->migrate = migrate;
+    oslot->node = node;
+    oslot->remote_name = sdsdup(remote_name);
+
+    return oslot;
+}
+
+static void cluster_open_slot_destroy(copen_slot *oslot)
+{
+    oslot->slot_num = 0;
+    oslot->migrate = 0;
+    oslot->node = NULL;
+
+    if(oslot->remote_name != NULL){
+        sdsfree(oslot->remote_name);
+        oslot->remote_name = NULL;
+    }
+    
+    hi_free(oslot);
+}
+
+/**
+  * Return a new node with the "cluster slots" command reply.
+  */
+static cluster_node *node_get_with_slots(
+    redisClusterContext *cc, redisReply *host_elem, 
+    redisReply *port_elem, uint8_t role)
+{
+    cluster_node *node = NULL;
+
+    if(host_elem == NULL || port_elem == NULL){
+        return NULL;
+    }
+
+    if(host_elem->type != REDIS_REPLY_STRING ||
+        host_elem->len <= 0){
+        __redisClusterSetError(cc, REDIS_ERR_OTHER, 
+            "Command(cluster slots) reply error: "
+            "node ip is not string.");
+        goto error;
+    }
+
+    if(port_elem->type != REDIS_REPLY_INTEGER ||
+        port_elem->integer <= 0){
+        __redisClusterSetError(cc, REDIS_ERR_OTHER, 
+            "Command(cluster slots) reply error: "
+            "node port is not integer.");
+        goto error;
+    }
+
+    if(!hi_valid_port((int)port_elem->integer)){
+        __redisClusterSetError(cc, REDIS_ERR_OTHER, 
+            "Command(cluster slots) reply error: "
+            "node port is not valid.");
+        goto error;
+    }
+
+    node = hi_alloc(sizeof(cluster_node));
+    if(node == NULL){
+        __redisClusterSetError(cc,
+            REDIS_ERR_OOM,"Out of memory");
+        goto error;
+    }
+    
+    cluster_node_init(node);
+
+    if(role == REDIS_ROLE_MASTER){
+        node->slots = listCreate();
+        if(node->slots == NULL){
+            hi_free(node);
+            __redisClusterSetError(cc,REDIS_ERR_OTHER,
+                "slots for node listCreate error");
+            goto error;
+        }
+
+        node->slots->free = listClusterSlotDestructor;
+    }
+    
+    node->name = NULL; 
+    node->addr = sdsnewlen(host_elem->str, host_elem->len);
+    node->addr = sdscatfmt(node->addr, ":%i", port_elem->integer);
+    
+    node->host = sdsnewlen(host_elem->str, host_elem->len);
+    node->port = (int)port_elem->integer;
+    node->role = role;
+    
+    return node;
+
+error:
+    
+    if(node != NULL){
+        hi_free(node);
+    }
+
+    return NULL;
+}
+
+/**
+  * Return a new node with the "cluster nodes" command reply.
+  */
+static cluster_node *node_get_with_nodes(
+    redisClusterContext *cc,
+    sds *node_infos, int info_count, uint8_t role)
+{
+    sds *ip_port = NULL;
+    int count_ip_port = 0;
+    cluster_node *node;
+
+    if(info_count < 8)
+    {
+        return NULL;
+    }
+
+    node = hi_alloc(sizeof(cluster_node));
+    if(node == NULL)
+    {
+        __redisClusterSetError(cc,
+            REDIS_ERR_OOM,"Out of memory");
+        goto error;
+    }
+    
+    cluster_node_init(node);
+
+    if(role == REDIS_ROLE_MASTER)
+    {
+        node->slots = listCreate();
+        if(node->slots == NULL)
+        {
+            hi_free(node);
+            __redisClusterSetError(cc,REDIS_ERR_OTHER,
+                "slots for node listCreate error");
+            goto error;
+        }
+
+        node->slots->free = listClusterSlotDestructor;
+    }
+    
+    node->name = node_infos[0]; 
+    node->addr = node_infos[1];
+    
+    ip_port = sdssplitlen(node_infos[1], sdslen(node_infos[1]), 
+        IP_PORT_SEPARATOR, strlen(IP_PORT_SEPARATOR), &count_ip_port);
+    if(ip_port == NULL || count_ip_port != 2)
+    {
+        __redisClusterSetError(cc,REDIS_ERR_OTHER,
+            "split ip port error");
+        goto error;
+    }
+    node->host = ip_port[0];
+    node->port = hi_atoi(ip_port[1], sdslen(ip_port[1]));
+    node->role = role;
+
+    sdsfree(ip_port[1]);
+    free(ip_port);
+
+    node_infos[0] = NULL;
+    node_infos[1] = NULL;
+    
+    return node;
+
+error:
+    if(ip_port != NULL)
+    {
+        sdsfreesplitres(ip_port, count_ip_port);
+    }
+
+    if(node != NULL)
+    {
+        hi_free(node);
+    }
+
+    return NULL;
+}
+
+static void cluster_nodes_swap_ctx(dict *nodes_f, dict *nodes_t)
+{
+    dictIterator *di;
+    dictEntry *de_f, *de_t;
+    cluster_node *node_f, *node_t;
+    redisContext *c;
+    redisAsyncContext *ac;
+
+    if(nodes_f == NULL || nodes_t == NULL){
+        return;
+    }
+
+    di = dictGetIterator(nodes_t);
+    while((de_t = dictNext(di)) != NULL){
+        node_t = dictGetEntryVal(de_t);
+        if(node_t == NULL){
+            continue;
+        }
+        
+        de_f = dictFind(nodes_f, node_t->addr);
+        if(de_f == NULL){
+            continue;
+        }
+
+        node_f = dictGetEntryVal(de_f);
+        if(node_f->con != NULL){
+            c = node_f->con;
+            node_f->con = node_t->con;
+            node_t->con = c;
+        }
+
+        if(node_f->acon != NULL){
+            ac = node_f->acon;
+            node_f->acon = node_t->acon;
+            node_t->acon = ac;
+
+            node_t->acon->data = node_t;
+            if (node_f->acon)
+                node_f->acon->data = node_f;
+        }
+    }
+
+    dictReleaseIterator(di);
+    
+}
+
+static int
+cluster_slot_start_cmp(const void *t1, const void *t2)
+{
+    const cluster_slot **s1 = t1, **s2 = t2;
+
+    return (*s1)->start > (*s2)->start?1:-1;
+}
+
+static int
+cluster_master_slave_mapping_with_name(redisClusterContext *cc,
+    dict **nodes, cluster_node *node, sds master_name)
+{
+    int ret;
+    dictEntry *di;
+    cluster_node *node_old;
+    listNode *lnode;
+
+    if(node == NULL || master_name == NULL)
+    {
+        return REDIS_ERR;
+    }
+
+    if(*nodes == NULL)
+    {
+        *nodes = dictCreate(
+            &clusterNodesRefDictType, NULL);
+    }
+
+    di = dictFind(*nodes, master_name);
+    if(di == NULL)
+    {
+        ret = dictAdd(*nodes, 
+            sdsnewlen(master_name, sdslen(master_name)), node);
+        if(ret != DICT_OK)
+        {
+            __redisClusterSetError(cc,REDIS_ERR_OTHER,
+                "the address already exists in the nodes");
+            return REDIS_ERR;
+        }
+
+    }
+    else
+    {
+        node_old = dictGetEntryVal(di);
+        if(node_old == NULL)
+        {
+            __redisClusterSetError(cc,REDIS_ERR_OTHER,
+                "dict get value null");
+            return REDIS_ERR;
+        }
+
+        if(node->role == REDIS_ROLE_MASTER &&
+            node_old->role == REDIS_ROLE_MASTER)
+        {
+            __redisClusterSetError(cc,REDIS_ERR_OTHER,
+                "two masters have the same name");
+            return REDIS_ERR;
+        }
+        else if(node->role == REDIS_ROLE_MASTER
+            && node_old->role == REDIS_ROLE_SLAVE)
+        {
+            if(node->slaves == NULL)
+            {
+                node->slaves = listCreate();
+                if(node->slaves == NULL)
+                {
+                    __redisClusterSetError(cc,REDIS_ERR_OOM,
+                        "Out of memory");
+                    return REDIS_ERR;
+                }
+
+                node->slaves->free = 
+                    listClusterNodeDestructor;
+            }
+        
+            if(node_old->slaves != NULL)
+            {
+                node_old->slaves->free = NULL;
+                while(listLength(node_old->slaves) > 0)
+                {
+                    lnode = listFirst(node_old->slaves);
+                    listAddNodeHead(node->slaves, lnode->value);
+                    listDelNode(node_old->slaves, lnode);
+                }
+                listRelease(node_old->slaves);
+                node_old->slaves = NULL;
+            }
+
+            listAddNodeHead(node->slaves, node_old);
+
+            dictSetHashVal(*nodes, di, node);
+        }
+        else if(node->role == REDIS_ROLE_SLAVE)
+        {
+            if(node_old->slaves == NULL)
+            {
+                node_old->slaves = listCreate();
+                if(node_old->slaves == NULL)
+                {
+                    __redisClusterSetError(cc,REDIS_ERR_OOM,
+                        "Out of memory");
+                    return REDIS_ERR;
+                }
+
+                node_old->slaves->free = 
+                    listClusterNodeDestructor;
+            }
+
+            listAddNodeTail(node_old->slaves, node);
+        }
+        else
+        {
+            NOT_REACHED();
+        }
+    }
+                
+    return REDIS_OK;
+}
+
+/**
+  * Parse the "cluster slots" command reply to nodes dict.
+  */
+dict * 
+parse_cluster_slots(redisClusterContext *cc,
+    redisReply *reply, int flags)
+{
+    int ret;
+    cluster_slot *slot = NULL;
+    dict *nodes = NULL;
+    dictEntry *den;
+    redisReply *elem_slots;
+    redisReply *elem_slots_begin, *elem_slots_end;
+    redisReply *elem_nodes;
+    redisReply *elem_ip, *elem_port;
+    cluster_node *master = NULL, *slave;
+    sds address;
+    uint32_t i, idx;
+
+    if(reply == NULL){
+        return NULL;
+    }
+
+    nodes = dictCreate(&clusterNodesDictType, NULL);
+    if(nodes == NULL){
+        __redisClusterSetError(cc,REDIS_ERR_OOM,
+            "out of memory");
+        goto error;
+    }
+    
+    if(reply->type != REDIS_REPLY_ARRAY || reply->elements <= 0){
+        __redisClusterSetError(cc, REDIS_ERR_OTHER, 
+            "Command(cluster slots) reply error: "
+            "reply is not an array.");
+        goto error;
+    }
+
+    for(i = 0; i < reply->elements; i ++){
+        elem_slots = reply->element[i];
+        if(elem_slots->type != REDIS_REPLY_ARRAY || 
+            elem_slots->elements < 3){
+            __redisClusterSetError(cc, REDIS_ERR_OTHER, 
+                "Command(cluster slots) reply error: "
+                "first sub_reply is not an array.");
+            goto error;
+        }
+        
+        slot = cluster_slot_create(NULL);
+        if(slot == NULL){
+            __redisClusterSetError(cc, REDIS_ERR_OOM, 
+                "Slot create failed: out of memory.");
+            goto error;
+        }
+
+        //one slots region
+        for(idx = 0; idx < elem_slots->elements; idx ++){
+            if(idx == 0){
+                elem_slots_begin = elem_slots->element[idx];
+                if(elem_slots_begin->type != REDIS_REPLY_INTEGER){
+                    __redisClusterSetError(cc, REDIS_ERR_OTHER, 
+                        "Command(cluster slots) reply error: "
+                        "slot begin is not an integer.");
+                    goto error;
+                }
+                slot->start = (int)(elem_slots_begin->integer);
+            }else if(idx == 1){
+                elem_slots_end = elem_slots->element[idx];
+                if(elem_slots_end->type != REDIS_REPLY_INTEGER){
+                    __redisClusterSetError(cc, REDIS_ERR_OTHER, 
+                        "Command(cluster slots) reply error: "
+                        "slot end is not an integer.");
+                    goto error;
+                }
+                
+                slot->end = (int)(elem_slots_end->integer);
+
+                if(slot->start > slot->end){
+                    __redisClusterSetError(cc, REDIS_ERR_OTHER, 
+                        "Command(cluster slots) reply error: "
+                        "slot begin is bigger than slot end.");
+                    goto error;
+                }
+            }else{
+                elem_nodes = elem_slots->element[idx];
+                if(elem_nodes->type != REDIS_REPLY_ARRAY || 
+                    elem_nodes->elements != 3){
+                    __redisClusterSetError(cc, REDIS_ERR_OTHER, 
+                        "Command(cluster slots) reply error: "
+                        "nodes sub_reply is not an correct array.");
+                    goto error;
+                }
+
+                elem_ip = elem_nodes->element[0];
+                elem_port = elem_nodes->element[1];
+
+                if(elem_ip == NULL || elem_port == NULL ||
+                    elem_ip->type != REDIS_REPLY_STRING || 
+                    elem_port->type != REDIS_REPLY_INTEGER){
+                    __redisClusterSetError(cc, REDIS_ERR_OTHER, 
+                        "Command(cluster slots) reply error: "
+                        "master ip or port is not correct.");
+                    goto error;
+                }
+
+                //this is master.
+                if(idx == 2){
+                    address = sdsnewlen(elem_ip->str, elem_ip->len);
+                    address = sdscatfmt(address, ":%i", elem_port->integer);
+
+                    den = dictFind(nodes, address);
+                    //master already exits, break to the next slots region.
+                    if(den != NULL){
+                        sdsfree(address);
+
+                        master = dictGetEntryVal(den);
+                        ret = cluster_slot_ref_node(slot, master);
+                        if(ret != REDIS_OK){
+                            __redisClusterSetError(cc, REDIS_ERR_OOM, 
+                                "Slot ref node failed: out of memory.");
+                            goto error;
+                        }
+
+                        slot = NULL;
+                        break;
+                    }
+
+                    sdsfree(address);
+                    master = node_get_with_slots(cc, elem_ip, 
+                        elem_port, REDIS_ROLE_MASTER);
+                    if(master == NULL){
+                        goto error;
+                    }
+
+                    ret = dictAdd(nodes, 
+                        sdsnewlen(master->addr, sdslen(master->addr)), master);
+                    if(ret != DICT_OK){
+                        __redisClusterSetError(cc,REDIS_ERR_OTHER,
+                            "The address already exists in the nodes");
+                        cluster_node_deinit(master);
+                        hi_free(master);
+                        goto error;
+                    }
+                    
+                    ret = cluster_slot_ref_node(slot, master);
+                    if(ret != REDIS_OK){
+                        __redisClusterSetError(cc, REDIS_ERR_OOM, 
+                            "Slot ref node failed: out of memory.");
+                        goto error;
+                    }
+
+                    slot = NULL;
+                }else if(flags & HIRCLUSTER_FLAG_ADD_SLAVE){
+                    slave = node_get_with_slots(cc, elem_ip, 
+                            elem_port, REDIS_ROLE_SLAVE);
+                    if(slave == NULL){
+                        goto error;
+                    }
+
+                    if(master->slaves == NULL){
+                        master->slaves = listCreate();
+                        if(master->slaves == NULL){
+                            __redisClusterSetError(cc,REDIS_ERR_OOM,
+                                "Out of memory");
+                            cluster_node_deinit(slave);
+                            goto error;
+                        }
+
+                        master->slaves->free = 
+                            listClusterNodeDestructor;
+                    }
+
+                    listAddNodeTail(master->slaves, slave);
+                }
+            }
+        }
+    }
+
+    return nodes;
+
+error:
+
+    if(nodes != NULL){
+        dictRelease(nodes);
+    }
+
+    if(slot != NULL){
+        cluster_slot_destroy(slot);
+    }
+    
+    return NULL;
+}
+
+/**
+  * Parse the "cluster nodes" command reply to nodes dict.
+  */
+dict *
+parse_cluster_nodes(redisClusterContext *cc, 
+    char *str, int str_len, int flags)
+{
+    int ret;
+    dict *nodes = NULL;
+    dict *nodes_name = NULL;
+    cluster_node *master, *slave;
+    cluster_slot *slot;
+    char *pos, *start, *end, *line_start, *line_end;
+    char *role;
+    int role_len;
+    uint8_t myself = 0;
+    int slot_start, slot_end;
+    sds *part = NULL, *slot_start_end = NULL;
+    int count_part = 0, count_slot_start_end = 0;
+    int k;
+    int len;
+
+    nodes = dictCreate(&clusterNodesDictType, NULL);
+    if(nodes == NULL){
+        __redisClusterSetError(cc,REDIS_ERR_OOM,
+            "out of memory");
+        goto error;
+    }
+
+    start = str;
+    end = start + str_len;
+    
+    line_start = start;
+
+    for(pos = start; pos < end; pos ++){
+        if(*pos == '\n'){
+            line_end = pos - 1;
+            len = line_end - line_start;
+            
+            part = sdssplitlen(line_start, len + 1, " ", 1, &count_part);
+
+            if(part == NULL || count_part < 8){
+                __redisClusterSetError(cc,REDIS_ERR_OTHER,
+                    "split cluster nodes error");
+                goto error;
+            }
+
+            //the address string is ":0", skip this node.
+            if(sdslen(part[1]) == 2 && strcmp(part[1], ":0") == 0){
+                sdsfreesplitres(part, count_part);
+                count_part = 0;
+                part = NULL;
+                
+                start = pos + 1;
+                line_start = start;
+                pos = start;
+                
+                continue;
+            }
+
+            if(sdslen(part[2]) >= 7 && memcmp(part[2], "myself,", 7) == 0){
+                role_len = sdslen(part[2]) - 7;
+                role = part[2] + 7;
+                myself = 1;
+            }else{
+                role_len = sdslen(part[2]);
+                role = part[2];
+            }
+
+            //add master node
+            if(role_len >= 6 && memcmp(role, "master", 6) == 0){
+                if(count_part < 8){
+                    __redisClusterSetError(cc,REDIS_ERR_OTHER,
+                        "Master node parts number error: less than 8.");
+                    goto error;
+                }
+                
+                master = node_get_with_nodes(cc, 
+                    part, count_part, REDIS_ROLE_MASTER);
+                if(master == NULL){
+                    goto error;
+                }
+
+                ret = dictAdd(nodes, 
+                    sdsnewlen(master->addr, sdslen(master->addr)), master);
+                if(ret != DICT_OK){
+                    __redisClusterSetError(cc,REDIS_ERR_OTHER,
+                        "The address already exists in the nodes");
+                    cluster_node_deinit(master);
+                    hi_free(master);
+                    goto error;
+                }
+
+                if(flags & HIRCLUSTER_FLAG_ADD_SLAVE){
+                    ret = cluster_master_slave_mapping_with_name(cc, 
+                        &nodes_name, master, master->name);
+                    if(ret != REDIS_OK){
+                        cluster_node_deinit(master);
+                        hi_free(master);
+                        goto error;
+                    }
+                }
+
+                if(myself) master->myself = 1;
+                
+                for(k = 8; k < count_part; k ++){
+                    slot_start_end = sdssplitlen(part[k], 
+                        sdslen(part[k]), "-", 1, &count_slot_start_end);
+                    
+                    if(slot_start_end == NULL){
+                        __redisClusterSetError(cc,REDIS_ERR_OTHER,
+                            "split slot start end error(NULL)");
+                        goto error;
+                    }else if(count_slot_start_end == 1){
+                        slot_start = 
+                            hi_atoi(slot_start_end[0], sdslen(slot_start_end[0]));
+                        slot_end = slot_start;
+                    }else if(count_slot_start_end == 2){
+                        slot_start = 
+                            hi_atoi(slot_start_end[0], sdslen(slot_start_end[0]));;
+                        slot_end = 
+                            hi_atoi(slot_start_end[1], sdslen(slot_start_end[1]));;
+                    }else{
+                        //add open slot for master
+                        if(flags & HIRCLUSTER_FLAG_ADD_OPENSLOT && 
+                            count_slot_start_end == 3 && 
+                            sdslen(slot_start_end[0]) > 1 &&
+                            sdslen(slot_start_end[1]) == 1 && 
+                            sdslen(slot_start_end[2]) > 1 && 
+                            slot_start_end[0][0] == '[' && 
+                            slot_start_end[2][sdslen(slot_start_end[2])-1] == ']'){
+                            
+                            copen_slot *oslot, **oslot_elem;
+                            
+                            sdsrange(slot_start_end[0], 1, -1);
+                            sdsrange(slot_start_end[2], 0, -2);
+                            
+                            if(slot_start_end[1][0] == '>'){
+                                oslot = cluster_open_slot_create(
+                                    hi_atoi(slot_start_end[0],
+                                    sdslen(slot_start_end[0])), 
+                                    1, slot_start_end[2], master);
+                                if(oslot == NULL){
+                                    __redisClusterSetError(cc,REDIS_ERR_OTHER,
+                                        "create open slot error");
+                                    goto error;
+                                }
+ 
+                                if(master->migrating == NULL){
+                                    master->migrating = hiarray_create(1, sizeof(oslot));
+                                    if(master->migrating == NULL){
+                                        __redisClusterSetError(cc,REDIS_ERR_OTHER,
+                                            "create migrating array error");
+                                        cluster_open_slot_destroy(oslot);
+                                        goto error;
+                                    }
+                                }
+
+                                oslot_elem = hiarray_push(master->migrating);
+                                if(oslot_elem == NULL){
+                                    __redisClusterSetError(cc,REDIS_ERR_OTHER,
+                                        "Push migrating array error: out of memory");
+                                    cluster_open_slot_destroy(oslot);
+                                    goto error;
+                                }
+
+                                *oslot_elem = oslot;
+                            }else if(slot_start_end[1][0] == '<'){
+                                oslot = cluster_open_slot_create(hi_atoi(slot_start_end[0],
+                                    sdslen(slot_start_end[0])), 0, slot_start_end[2],
+                                    master);
+                                if(oslot == NULL){
+                                    __redisClusterSetError(cc,REDIS_ERR_OTHER,
+                                        "create open slot error");
+                                    goto error;
+                                }
+
+                                if(master->importing == NULL){
+                                    master->importing = hiarray_create(1, sizeof(oslot));
+                                    if(master->importing == NULL){
+                                        __redisClusterSetError(cc,REDIS_ERR_OTHER,
+                                            "create migrating array error");
+                                        cluster_open_slot_destroy(oslot);
+                                        goto error;
+                                    }
+                                }
+
+                                oslot_elem = hiarray_push(master->importing);
+                                if(oslot_elem == NULL){
+                                    __redisClusterSetError(cc,REDIS_ERR_OTHER,
+                                        "push migrating array error: out of memory");
+                                    cluster_open_slot_destroy(oslot);
+                                    goto error;
+                                }
+
+                                *oslot_elem = oslot;
+                            }
+                        }
+                        
+                        slot_start = -1;
+                        slot_end = -1;
+                    }
+                    
+                    sdsfreesplitres(slot_start_end, count_slot_start_end);
+                    count_slot_start_end = 0;
+                    slot_start_end = NULL;
+
+                    if(slot_start < 0 || slot_end < 0 || 
+                        slot_start > slot_end || slot_end >= REDIS_CLUSTER_SLOTS){
+                        continue;
+                    }
+
+                    slot = cluster_slot_create(master);
+                    if(slot == NULL){
+                        __redisClusterSetError(cc,REDIS_ERR_OOM,
+                            "Out of memory");
+                        goto error;
+                    }
+                    
+                    slot->start = (uint32_t)slot_start;
+                    slot->end = (uint32_t)slot_end;                    
+                }
+
+            }
+            //add slave node
+            else if((flags & HIRCLUSTER_FLAG_ADD_SLAVE) && 
+                (role_len >= 5 && memcmp(role, "slave", 5) == 0)){
+                slave = node_get_with_nodes(cc, part, 
+                    count_part, REDIS_ROLE_SLAVE);
+                if(slave == NULL){
+                    goto error;
+                }
+
+                ret = cluster_master_slave_mapping_with_name(cc, 
+                    &nodes_name, slave, part[3]);
+                if(ret != REDIS_OK){
+                    cluster_node_deinit(slave);
+                    hi_free(slave);
+                    goto error;
+                }
+
+                if(myself) slave->myself = 1;
+            }
+
+            if(myself == 1){
+                myself = 0;
+            }
+
+            sdsfreesplitres(part, count_part);
+            count_part = 0;
+            part = NULL;
+            
+            start = pos + 1;
+            line_start = start;
+            pos = start;
+        }
+    }
+
+    if(nodes_name != NULL){
+        dictRelease(nodes_name);
+    }
+    
+    return nodes;
+
+error:
+        
+    if(part != NULL){
+        sdsfreesplitres(part, count_part);
+        count_part = 0;
+        part = NULL;
+    }
+
+    if(slot_start_end != NULL){
+        sdsfreesplitres(slot_start_end, count_slot_start_end);
+        count_slot_start_end = 0;
+        slot_start_end = NULL;
+    }
+
+    if(nodes != NULL){
+        dictRelease(nodes);
+    }
+
+    if(nodes_name != NULL){
+        dictRelease(nodes_name);
+    }
+    
+    return NULL;
+}
+
+/**
+  * Update route with the "cluster nodes" or "cluster slots" command reply.
+  */
+static int 
+cluster_update_route_by_addr(redisClusterContext *cc, 
+    const char *ip, int port)
+{
+    redisContext *c = NULL;
+    redisReply *reply = NULL;
+    dict *nodes = NULL;
+    struct hiarray *slots = NULL;
+    cluster_node *master;
+    cluster_slot *slot, **slot_elem;
+    dictIterator *dit = NULL;
+    dictEntry *den;
+    listIter *lit = NULL;
+    listNode *lnode;
+    cluster_node *table[REDIS_CLUSTER_SLOTS];
+    uint32_t j, k;
+
+    if(cc == NULL){
+        return REDIS_ERR;
+    }
+
+    if(ip == NULL || port <= 0){
+        __redisClusterSetError(cc,
+            REDIS_ERR_OTHER,"Ip or port error!");
+        goto error;
+    }
+
+    if(cc->timeout){
+        c = redisConnectWithTimeout(ip, port, *cc->timeout);
+    }else{
+        c = redisConnect(ip, port);
+    }
+        
+    if (c == NULL){
+        __redisClusterSetError(cc,REDIS_ERR_OTHER,
+            "Init redis context error(return NULL)");
+        goto error;
+    }else if(c->err){
+        __redisClusterSetError(cc,c->err,c->errstr);
+        goto error;
+    }
+
+    if(cc->flags & HIRCLUSTER_FLAG_ROUTE_USE_SLOTS){
+        reply = redisCommand(c, REDIS_COMMAND_CLUSTER_SLOTS);
+        if(reply == NULL){
+            __redisClusterSetError(cc,REDIS_ERR_OTHER,
+                "Command(cluster slots) reply error(NULL).");
+            goto error;
+        }else if(reply->type != REDIS_REPLY_ARRAY){
+            if(reply->type == REDIS_REPLY_ERROR){
+                __redisClusterSetError(cc,REDIS_ERR_OTHER,
+                    reply->str);
+            }else{
+                __redisClusterSetError(cc,REDIS_ERR_OTHER,
+                    "Command(cluster slots) reply error: type is not array.");
+            }
+            
+            goto error;
+        }
+
+        nodes = parse_cluster_slots(cc, reply, cc->flags);
+    }else{
+        reply = redisCommand(c, REDIS_COMMAND_CLUSTER_NODES);
+        if(reply == NULL){
+            __redisClusterSetError(cc,REDIS_ERR_OTHER,
+                "Command(cluster nodes) reply error(NULL).");
+            goto error;
+        }else if(reply->type != REDIS_REPLY_STRING){
+            if(reply->type == REDIS_REPLY_ERROR){
+                __redisClusterSetError(cc,REDIS_ERR_OTHER,
+                    reply->str);
+            }else{
+                __redisClusterSetError(cc,REDIS_ERR_OTHER,
+                    "Command(cluster nodes) reply error: type is not string.");
+            }
+            
+            goto error;
+        }
+
+        nodes = parse_cluster_nodes(cc, reply->str, reply->len, cc->flags);
+    }
+
+    if(nodes == NULL){
+        goto error;
+    }
+    
+    memset(table, 0, REDIS_CLUSTER_SLOTS*sizeof(cluster_node *));
+    
+    slots = hiarray_create(dictSize(nodes), sizeof(cluster_slot*));
+    if(slots == NULL){
+        __redisClusterSetError(cc,REDIS_ERR_OTHER,
+            "Slots array create failed: out of memory");
+        goto error;
+    }
+    
+    dit = dictGetIterator(nodes);
+    if(dit == NULL){
+        __redisClusterSetError(cc,REDIS_ERR_OOM,
+            "Dict get iterator failed: out of memory");
+        goto error;
+    }
+    
+    while((den = dictNext(dit))){
+        master = dictGetEntryVal(den);
+        if(master->role != REDIS_ROLE_MASTER){
+            __redisClusterSetError(cc,REDIS_ERR_OOM,
+                "Node role must be master");
+            goto error;
+        }
+
+        if(master->slots == NULL){
+            continue;
+        }
+        
+        lit = listGetIterator(master->slots, AL_START_HEAD);
+        if(lit == NULL){
+            __redisClusterSetError(cc, REDIS_ERR_OOM,
+                "List get iterator failed: out of memory");
+            goto error;
+        }
+        
+        while((lnode = listNext(lit))){
+            slot = listNodeValue(lnode);
+            if(slot->start > slot->end || 
+                slot->end >= REDIS_CLUSTER_SLOTS){
+                __redisClusterSetError(cc, REDIS_ERR_OTHER,
+                    "Slot region for node is error");
+                goto error;
+            }
+            
+            slot_elem = hiarray_push(slots);
+            *slot_elem = slot;
+        }
+
+        listReleaseIterator(lit);
+    }
+
+    dictReleaseIterator(dit);
+
+    hiarray_sort(slots, cluster_slot_start_cmp);
+    for(j = 0; j < hiarray_n(slots); j ++){
+        slot_elem = hiarray_get(slots, j);
+        
+        for(k = (*slot_elem)->start; k <= (*slot_elem)->end; k ++){
+            if(table[k] != NULL){
+                __redisClusterSetError(cc, REDIS_ERR_OTHER,
+                    "Diffent node hold a same slot");
+                goto error;
+            }
+            
+            table[k] = (*slot_elem)->node;
+        }
+    }
+    
+    cluster_nodes_swap_ctx(cc->nodes, nodes);
+    if(cc->nodes != NULL){
+        dictRelease(cc->nodes);
+        cc->nodes = NULL;
+    }
+    cc->nodes = nodes;
+
+    if(cc->slots != NULL)
+    {
+        cc->slots->nelem = 0;
+        hiarray_destroy(cc->slots);
+        cc->slots = NULL;
+    }
+    cc->slots = slots;
+
+    memcpy(cc->table, table, REDIS_CLUSTER_SLOTS*sizeof(cluster_node *));
+    cc->route_version ++;
+    
+    freeReplyObject(reply);
+
+    if(c != NULL){
+        redisFree(c);
+    }
+    
+    return REDIS_OK;
+
+error:
+
+    if(dit != NULL){
+        dictReleaseIterator(dit);
+    }
+
+    if(lit != NULL){
+        listReleaseIterator(lit);    
+    }
+
+    if(slots != NULL)
+    {
+        if(slots == cc->slots)
+        {
+            cc->slots = NULL;
+        }
+        
+        slots->nelem = 0;
+        hiarray_destroy(slots);
+    }
+
+    if(nodes != NULL){
+        if(nodes == cc->nodes){
+            cc->nodes = NULL;
+        }
+
+        dictRelease(nodes);
+    }
+
+    if(reply != NULL){
+        freeReplyObject(reply);
+        reply = NULL;
+    }
+
+    if(c != NULL){
+        redisFree(c);
+    }
+    
+    return REDIS_ERR;
+}
+
+
+/**
+  * Update route with the "cluster nodes" command reply.
+  */
+static int 
+cluster_update_route_with_nodes_old(redisClusterContext *cc, 
+    const char *ip, int port)
+{
+    int ret;
+    redisContext *c = NULL;
+    redisReply *reply = NULL;
+    struct hiarray *slots = NULL;
+    dict *nodes = NULL;
+    dict *nodes_name = NULL;
+    cluster_node *master, *slave;
+    cluster_slot **slot;
+    char *pos, *start, *end, *line_start, *line_end;
+    char *role;
+    int role_len;
+    uint8_t myself = 0;
+    int slot_start, slot_end;
+    sds *part = NULL, *slot_start_end = NULL;
+    int count_part = 0, count_slot_start_end = 0;
+    int j, k;
+    int len;
+    cluster_node *table[REDIS_CLUSTER_SLOTS] = {NULL};
+
+    if(cc == NULL)
+    {
+        return REDIS_ERR;
+    }
+
+    if(ip == NULL || port <= 0)
+    {
+        __redisClusterSetError(cc,
+            REDIS_ERR_OTHER,"ip or port error!");
+        goto error;
+    }
+
+    if(cc->timeout)
+    {
+        c = redisConnectWithTimeout(ip, port, *cc->timeout);
+    }
+    else
+    {
+        c = redisConnect(ip, port);
+    }
+        
+    if (c == NULL)
+    {
+        __redisClusterSetError(cc,REDIS_ERR_OTHER,
+            "init redis context error(return NULL)");
+        goto error;
+    }
+    else if(c->err)
+    {
+        __redisClusterSetError(cc,c->err,c->errstr);
+        goto error;
+    }
+
+    reply = redisCommand(c, REDIS_COMMAND_CLUSTER_NODES);
+
+    if(reply == NULL)
+    {
+        __redisClusterSetError(cc,REDIS_ERR_OTHER,
+            "command(cluster nodes) reply error(NULL)");
+        goto error;
+    }
+    else if(reply->type != REDIS_REPLY_STRING)
+    {
+        if(reply->type == REDIS_REPLY_ERROR)
+        {
+            __redisClusterSetError(cc,REDIS_ERR_OTHER,
+                reply->str);
+        }
+        else
+        {
+            __redisClusterSetError(cc,REDIS_ERR_OTHER,
+                "command(cluster nodes) reply error(type is not string)");
+        }
+        
+        goto error;
+    }
+
+    nodes = dictCreate(&clusterNodesDictType, NULL);
+    
+    slots = hiarray_create(10, sizeof(cluster_slot*));
+    if(slots == NULL)
+    {
+        __redisClusterSetError(cc,REDIS_ERR_OTHER,
+            "array create error");
+        goto error;
+    }
+
+    start = reply->str;
+    end = start + reply->len;
+    
+    line_start = start;
+
+    for(pos = start; pos < end; pos ++)
+    {
+        if(*pos == '\n')
+        {
+            line_end = pos - 1;
+            len = line_end - line_start;
+            
+            part = sdssplitlen(line_start, len + 1, " ", 1, &count_part);
+
+            if(part == NULL || count_part < 8)
+            {
+                __redisClusterSetError(cc,REDIS_ERR_OTHER,
+                    "split cluster nodes error");
+                goto error;
+            }
+
+            //the address string is ":0", skip this node.
+            if(sdslen(part[1]) == 2 && strcmp(part[1], ":0") == 0)
+            {
+                sdsfreesplitres(part, count_part);
+                count_part = 0;
+                part = NULL;
+                
+                start = pos + 1;
+                line_start = start;
+                pos = start;
+                
+                continue;
+            }
+
+            if(sdslen(part[2]) >= 7 && memcmp(part[2], "myself,", 7) == 0)
+            {
+                role_len = sdslen(part[2]) - 7;
+                role = part[2] + 7;
+                myself = 1;
+            }
+            else
+            {
+                role_len = sdslen(part[2]);
+                role = part[2];
+            }
+
+            //add master node
+            if(role_len >= 6 && memcmp(role, "master", 6) == 0)
+            {
+                if(count_part < 8)
+                {
+                    __redisClusterSetError(cc,REDIS_ERR_OTHER,
+                        "master node part number error");
+                    goto error;
+                }
+                
+                master = node_get_with_nodes(cc, 
+                    part, count_part, REDIS_ROLE_MASTER);
+                if(master == NULL)
+                {
+                    goto error;
+                }
+
+                ret = dictAdd(nodes, 
+                    sdsnewlen(master->addr, sdslen(master->addr)), master);
+                if(ret != DICT_OK)
+                {
+                    __redisClusterSetError(cc,REDIS_ERR_OTHER,
+                        "the address already exists in the nodes");
+                    cluster_node_deinit(master);
+                    hi_free(master);
+                    goto error;
+                }
+
+                if(cc->flags & HIRCLUSTER_FLAG_ADD_SLAVE)
+                {
+                    ret = cluster_master_slave_mapping_with_name(cc, 
+                        &nodes_name, master, master->name);
+                    if(ret != REDIS_OK)
+                    {
+                        cluster_node_deinit(master);
+                        hi_free(master);
+                        goto error;
+                    }
+                }
+                
+                if(myself == 1)
+                {
+                    master->con = c;
+                    c = NULL;
+                }
+                
+                for(k = 8; k < count_part; k ++)
+                {
+                    slot_start_end = sdssplitlen(part[k], 
+                        sdslen(part[k]), "-", 1, &count_slot_start_end);
+                    
+                    if(slot_start_end == NULL)
+                    {
+                        __redisClusterSetError(cc,REDIS_ERR_OTHER,
+                            "split slot start end error(NULL)");
+                        goto error;
+                    }
+                    else if(count_slot_start_end == 1)
+                    {
+                        slot_start = 
+                            hi_atoi(slot_start_end[0], sdslen(slot_start_end[0]));
+                        slot_end = slot_start;
+                    }
+                    else if(count_slot_start_end == 2)
+                    {
+                        slot_start = 
+                            hi_atoi(slot_start_end[0], sdslen(slot_start_end[0]));;
+                        slot_end = 
+                            hi_atoi(slot_start_end[1], sdslen(slot_start_end[1]));;
+                    }
+                    else
+                    {
+                        slot_start = -1;
+                        slot_end = -1;
+                    }
+                    
+                    sdsfreesplitres(slot_start_end, count_slot_start_end);
+                    count_slot_start_end = 0;
+                    slot_start_end = NULL;
+
+                    if(slot_start < 0 || slot_end < 0 || 
+                        slot_start > slot_end || slot_end >= REDIS_CLUSTER_SLOTS)
+                    {
+                        continue;
+                    }
+
+                    for(j = slot_start; j <= slot_end; j ++)
+                    {
+                        if(table[j] != NULL)
+                        {
+                            __redisClusterSetError(cc,REDIS_ERR_OTHER,
+                                "diffent node hold a same slot");
+                            goto error;
+                        }
+                        table[j] = master;
+                    }
+                    
+                    slot = hiarray_push(slots);
+                    if(slot == NULL)
+                    {
+                        __redisClusterSetError(cc,REDIS_ERR_OTHER,
+                            "slot push in array error");
+                        goto error;
+                    }
+
+                    *slot = cluster_slot_create(master);
+                    if(*slot == NULL)
+                    {
+                        __redisClusterSetError(cc,REDIS_ERR_OOM,
+                            "Out of memory");
+                        goto error;
+                    }
+
+                    (*slot)->start = (uint32_t)slot_start;
+                    (*slot)->end = (uint32_t)slot_end;                    
+                }
+
+            }
+            //add slave node
+            else if((cc->flags & HIRCLUSTER_FLAG_ADD_SLAVE) && 
+                (role_len >= 5 && memcmp(role, "slave", 5) == 0))
+            {
+                slave = node_get_with_nodes(cc, part, 
+                    count_part, REDIS_ROLE_SLAVE);
+                if(slave == NULL)
+                {
+                    goto error;
+                }
+
+                ret = cluster_master_slave_mapping_with_name(cc, 
+                    &nodes_name, slave, part[3]);
+                if(ret != REDIS_OK)
+                {
+                    cluster_node_deinit(slave);
+                    hi_free(slave);
+                    goto error;
+                }
+                
+                if(myself == 1)
+                {
+                    slave->con = c;
+                    c = NULL;
+                }
+            }
+
+            if(myself == 1)
+            {
+                myself = 0;
+            }
+
+            sdsfreesplitres(part, count_part);
+            count_part = 0;
+            part = NULL;
+            
+            start = pos + 1;
+            line_start = start;
+            pos = start;
+        }
+    }
+
+    if(cc->slots != NULL)
+    {
+        cc->slots->nelem = 0;
+        hiarray_destroy(cc->slots);
+        cc->slots = NULL;
+    }
+    cc->slots = slots;
+
+    cluster_nodes_swap_ctx(cc->nodes, nodes);
+
+    if(cc->nodes != NULL)
+    {
+        dictRelease(cc->nodes);
+        cc->nodes = NULL;
+    }
+    cc->nodes = nodes;
+
+    hiarray_sort(cc->slots, cluster_slot_start_cmp);
+
+    memcpy(cc->table, table, REDIS_CLUSTER_SLOTS*sizeof(cluster_node *));
+    cc->route_version ++;
+    
+    freeReplyObject(reply);
+
+    if(c != NULL)
+    {
+        redisFree(c);
+    }
+
+    if(nodes_name != NULL)
+    {
+        dictRelease(nodes_name);
+    }
+    
+    return REDIS_OK;
+
+error:
+        
+    if(part != NULL)
+    {
+        sdsfreesplitres(part, count_part);
+        count_part = 0;
+        part = NULL;
+    }
+
+    if(slot_start_end != NULL)
+    {
+        sdsfreesplitres(slot_start_end, count_slot_start_end);
+        count_slot_start_end = 0;
+        slot_start_end = NULL;
+    }
+
+    if(slots != NULL)
+    {
+        if(slots == cc->slots)
+        {
+            cc->slots = NULL;
+        }
+
+        slots->nelem = 0;
+        hiarray_destroy(slots);
+    }
+
+    if(nodes != NULL)
+    {
+        if(nodes == cc->nodes)
+        {
+            cc->nodes = NULL;
+        }
+
+        dictRelease(nodes);
+    }
+
+    if(nodes_name != NULL)
+    {
+        dictRelease(nodes_name);
+    }
+
+    if(reply != NULL)
+    {
+        freeReplyObject(reply);
+        reply = NULL;
+    }
+
+    if(c != NULL)
+    {
+        redisFree(c);
+    }
+    
+    return REDIS_ERR;
+}
+
+int
+cluster_update_route(redisClusterContext *cc)
+{
+    int ret;
+    int flag_err_not_set = 1;
+    cluster_node *node;
+    dictIterator *it;
+    dictEntry *de;
+    
+    if(cc == NULL)
+    {
+        return REDIS_ERR;
+    }
+
+    if(cc->ip != NULL && cc->port > 0)
+    {
+        ret = cluster_update_route_by_addr(cc, cc->ip, cc->port);
+        if(ret == REDIS_OK)
+        {
+            return REDIS_OK;
+        }
+
+        flag_err_not_set = 0;
+    }
+
+    if(cc->nodes == NULL)
+    {
+        if(flag_err_not_set)
+        {
+            __redisClusterSetError(cc, REDIS_ERR_OTHER, "no server address");
+        }
+        
+        return REDIS_ERR;
+    }
+
+    it = dictGetIterator(cc->nodes);
+    while ((de = dictNext(it)) != NULL)
+    {
+        node = dictGetEntryVal(de);
+        if(node == NULL || node->host == NULL || node->port < 0)
+        {
+            continue;
+        }
+
+        ret = cluster_update_route_by_addr(cc, node->host, node->port);
+        if(ret == REDIS_OK)
+        {
+            if(cc->err)
+            {
+                cc->err = 0;
+                memset(cc->errstr, '\0', strlen(cc->errstr));
+            }
+            
+            dictReleaseIterator(it);
+            return REDIS_OK;
+        }
+
+        flag_err_not_set = 0;
+    }
+    
+    dictReleaseIterator(it);
+
+    if(flag_err_not_set)
+    {
+        __redisClusterSetError(cc, REDIS_ERR_OTHER, "no valid server address");
+    }
+
+    return REDIS_ERR;
+}
+
+static void print_cluster_node_list(redisClusterContext *cc)
+{
+    dictIterator *di = NULL;
+    dictEntry *de;
+    listIter *it;
+    listNode *ln;
+    cluster_node *master, *slave;
+    hilist *slaves;
+
+    if(cc == NULL)
+    {
+        return;
+    }
+
+    di = dictGetIterator(cc->nodes);
+
+    printf("name\taddress\trole\tslaves\n");
+    
+    while((de = dictNext(di)) != NULL) {
+        master = dictGetEntryVal(de);
+
+        printf("%s\t%s\t%d\t%s\n",master->name, master->addr, 
+            master->role, master->slaves?"hava":"null");
+
+        slaves = master->slaves;
+        if(slaves == NULL)
+        {
+            continue;
+        }
+        
+        it = listGetIterator(slaves, AL_START_HEAD);
+        while((ln = listNext(it)) != NULL)
+        {
+            slave = listNodeValue(ln);
+            printf("%s\t%s\t%d\t%s\n",slave->name, slave->addr, 
+                slave->role, slave->slaves?"hava":"null");
+        }
+
+        listReleaseIterator(it);
+
+        printf("\n");
+    }
+}
+
+
+int test_cluster_update_route(redisClusterContext *cc)
+{
+    int ret;
+    
+    ret = cluster_update_route(cc);
+
+    //print_cluster_node_list(cc);
+    
+    return ret;
+}
+
+static redisClusterContext *redisClusterContextInit(void) {
+    redisClusterContext *cc;
+
+    cc = calloc(1,sizeof(redisClusterContext));
+    if (cc == NULL)
+        return NULL;
+
+    cc->err = 0;
+    cc->errstr[0] = '\0';
+    cc->ip = NULL;
+    cc->port = 0;
+    cc->flags = 0;
+    cc->timeout = NULL;
+    cc->nodes = NULL;
+    cc->slots = NULL;
+    cc->max_redirect_count = CLUSTER_DEFAULT_MAX_REDIRECT_COUNT;
+    cc->retry_count = 0;
+    cc->requests = NULL;
+    cc->need_update_route = 0;
+    cc->update_route_time = 0LL;
+
+    cc->route_version = 0LL;
+
+    memset(cc->table, 0, REDIS_CLUSTER_SLOTS*sizeof(cluster_node *));
+    
+    return cc;
+}
+
+void redisClusterFree(redisClusterContext *cc) {
+    
+    if (cc == NULL)
+        return;
+
+    if(cc->ip)
+    {
+        sdsfree(cc->ip);
+        cc->ip = NULL;
+    }
+
+    if (cc->timeout)
+    {
+        free(cc->timeout);
+    }
+
+    memset(cc->table, 0, REDIS_CLUSTER_SLOTS*sizeof(cluster_node *));
+
+    if(cc->slots != NULL)
+    {
+        cc->slots->nelem = 0;
+        hiarray_destroy(cc->slots);
+        cc->slots = NULL;
+    }
+
+    if(cc->nodes != NULL)
+    {
+        dictRelease(cc->nodes);
+    }
+
+    if(cc->requests != NULL)
+    {
+        listRelease(cc->requests);
+    }
+    
+    free(cc);
+}
+
+static int redisClusterAddNode(redisClusterContext *cc, const char *addr)
+{
+    dictEntry *node_entry;
+    cluster_node *node;
+    sds *ip_port = NULL;
+    int ip_port_count = 0;
+    sds ip;
+    int port;
+    
+    if(cc == NULL)
+    {
+        return REDIS_ERR;
+    }
+
+    if(cc->nodes == NULL)
+    {
+        cc->nodes = dictCreate(&clusterNodesDictType, NULL);
+        if(cc->nodes == NULL)
+        {
+            return REDIS_ERR;
+        }
+    }
+
+    node_entry = dictFind(cc->nodes, addr);
+    if(node_entry == NULL)
+    {
+        ip_port = sdssplitlen(addr, strlen(addr), 
+            IP_PORT_SEPARATOR, strlen(IP_PORT_SEPARATOR), &ip_port_count);
+        if(ip_port == NULL || ip_port_count != 2 || 
+            sdslen(ip_port[0]) <= 0 || sdslen(ip_port[1]) <= 0)
+        {
+            if(ip_port != NULL)
+            {
+                sdsfreesplitres(ip_port, ip_port_count);
+            }
+            __redisClusterSetError(cc,REDIS_ERR_OTHER,"server address is error(correct is like: 127.0.0.1:1234)");
+            return REDIS_ERR;
+        }
+
+        ip = ip_port[0];
+        port = hi_atoi(ip_port[1], sdslen(ip_port[1]));
+
+        if(port <= 0)
+        {
+            sdsfreesplitres(ip_port, ip_port_count);
+            __redisClusterSetError(cc,REDIS_ERR_OTHER,"server port is error");
+            return REDIS_ERR;
+        }
+
+        sdsfree(ip_port[1]);
+        free(ip_port);
+        ip_port = NULL;
+    
+        node = hi_alloc(sizeof(cluster_node));
+        if(node == NULL)
+        {
+            sdsfree(ip);
+            __redisClusterSetError(cc,REDIS_ERR_OTHER,"alloc cluster node error");
+            return REDIS_ERR;
+        }
+
+        cluster_node_init(node);
+
+        node->addr = sdsnew(addr);
+        if(node->addr == NULL)
+        {
+            sdsfree(ip);
+            hi_free(node);
+            __redisClusterSetError(cc,REDIS_ERR_OTHER,"new node address error");
+            return REDIS_ERR;
+        }
+
+        node->host = ip;
+        node->port = port;
+
+        dictAdd(cc->nodes, sdsnewlen(node->addr, sdslen(node->addr)), node);
+    }
+    
+    return REDIS_OK;
+}
+
+
+/* Connect to a Redis cluster. On error the field error in the returned
+ * context will be set to the return value of the error function.
+ * When no set of reply functions is given, the default set will be used. */
+static redisClusterContext *_redisClusterConnect(redisClusterContext *cc, const char *addrs) {
+
+    int ret;
+    sds *address = NULL;
+    int address_count = 0;
+    int i;
+
+    if(cc == NULL)
+    {
+        return NULL;
+    }
+    
+
+    address = sdssplitlen(addrs, strlen(addrs), CLUSTER_ADDRESS_SEPARATOR, 
+        strlen(CLUSTER_ADDRESS_SEPARATOR), &address_count);
+    if(address == NULL || address_count <= 0)
+    {
+        __redisClusterSetError(cc,REDIS_ERR_OTHER,"servers address is error(correct is like: 127.0.0.1:1234,127.0.0.2:5678)");
+        return cc;
+    }
+
+    for(i = 0; i < address_count; i ++)
+    {
+        ret = redisClusterAddNode(cc, address[i]);
+        if(ret != REDIS_OK)
+        {
+            sdsfreesplitres(address, address_count);
+            return cc;
+        }
+    }
+
+    sdsfreesplitres(address, address_count);
+    
+    cluster_update_route(cc);
+
+    return cc;
+}
+
+redisClusterContext *redisClusterConnect(const char *addrs, int flags)
+{
+    redisClusterContext *cc;
+
+    cc = redisClusterContextInit();
+
+    if(cc == NULL)
+    {
+        return NULL;
+    }
+
+    cc->flags |= REDIS_BLOCK;
+    if(flags)
+    {
+        cc->flags |= flags;
+    }
+    
+    return _redisClusterConnect(cc, addrs);
+}
+
+redisClusterContext *redisClusterConnectWithTimeout(
+    const char *addrs, const struct timeval tv, int flags)
+{
+    redisClusterContext *cc;
+
+    cc = redisClusterContextInit();
+
+    if(cc == NULL)
+    {
+        return NULL;
+    }
+
+    cc->flags |= REDIS_BLOCK;
+    if(flags)
+    {
+        cc->flags |= flags;
+    }
+    
+    if (cc->timeout == NULL)
+    {
+        cc->timeout = malloc(sizeof(struct timeval));
+    }
+    
+    memcpy(cc->timeout, &tv, sizeof(struct timeval));
+    
+    return _redisClusterConnect(cc, addrs);
+}
+
+redisClusterContext *redisClusterConnectNonBlock(const char *addrs, int flags) {
+
+    redisClusterContext *cc;
+
+    cc = redisClusterContextInit();
+
+    if(cc == NULL)
+    {
+        return NULL;
+    }
+
+    cc->flags &= ~REDIS_BLOCK;
+    if(flags)
+    {
+        cc->flags |= flags;
+    }
+    
+    return _redisClusterConnect(cc, addrs);
+}
+
+redisContext *ctx_get_by_node(cluster_node *node, 
+    const struct timeval *timeout, int flags)
+{
+    redisContext *c = NULL;
+    if(node == NULL)
+    {
+        return NULL;
+    }
+
+    c = node->con;
+    if(c != NULL)
+    {
+        if(c->err)
+        {
+            redisReconnect(c);
+        }
+
+        return c;
+    }
+
+    if(node->host == NULL || node->port <= 0)
+    {
+        return NULL;
+    }
+
+    if(flags & REDIS_BLOCK)
+    {
+        if(timeout)
+        {
+            c = redisConnectWithTimeout(node->host, node->port, *timeout);
+        }
+        else
+        {
+            c = redisConnect(node->host, node->port);
+        }
+    }
+    else
+    {
+        c = redisConnectNonBlock(node->host, node->port);
+    }
+
+    node->con = c;
+
+    return c;
+}
+
+static cluster_node *node_get_by_slot(redisClusterContext *cc, uint32_t slot_num)
+{
+    struct hiarray *slots;
+    uint32_t slot_count;
+    cluster_slot **slot;
+    uint32_t middle, start, end;
+    uint8_t stop = 0;
+    
+    if(cc == NULL)
+    {
+        return NULL;
+    }
+
+    if(slot_num >= REDIS_CLUSTER_SLOTS)
+    {
+        return NULL;
+    }
+
+    slots = cc->slots;
+    if(slots == NULL)
+    {
+        return NULL;
+    }
+    slot_count = hiarray_n(slots);
+
+    start = 0;
+    end = slot_count - 1;
+    middle = 0;
+
+    do{
+        if(start >= end)
+        {
+            stop = 1;
+            middle = end;
+        }
+        else
+        {
+            middle = start + (end - start)/2;
+        }
+
+        ASSERT(middle < slot_count);
+
+        slot = hiarray_get(slots, middle);
+        if((*slot)->start > slot_num)
+        {
+            end = middle - 1;
+        }
+        else if((*slot)->end < slot_num)
+        {
+            start = middle + 1;
+        }
+        else
+        {
+            return (*slot)->node;
+        }
+            
+        
+    }while(!stop);
+
+    printf("slot_num : %d\n", slot_num);
+    printf("slot_count : %d\n", slot_count);
+    printf("start : %d\n", start);
+    printf("end : %d\n", end);
+    printf("middle : %d\n", middle);
+
+    return NULL;
+}
+
+
+static cluster_node *node_get_by_table(redisClusterContext *cc, uint32_t slot_num)
+{   
+    if(cc == NULL)
+    {
+        return NULL;
+    }
+
+    if(slot_num >= REDIS_CLUSTER_SLOTS)
+    {
+        return NULL;
+    }
+
+    return cc->table[slot_num];
+    
+}
+
+static cluster_node *node_get_witch_connected(redisClusterContext *cc)
+{
+    dictIterator *di;
+    dictEntry *de;
+    struct cluster_node *node;
+    redisContext *c = NULL;
+    redisReply *reply = NULL;
+
+    if(cc == NULL || cc->nodes == NULL)
+    {
+        return NULL;
+    }
+
+    di = dictGetIterator(cc->nodes);
+    while((de = dictNext(di)) != NULL)
+    {
+        node = dictGetEntryVal(de);
+        if(node == NULL)
+        {
+            continue;
+        }
+        
+        c = ctx_get_by_node(node, cc->timeout, REDIS_BLOCK);
+        if(c == NULL || c->err)
+        {
+            continue;
+        }
+
+        reply = redisCommand(c, REDIS_COMMAND_PING);
+        if(reply != NULL && reply->type == REDIS_REPLY_STATUS &&
+            reply->str != NULL && strcmp(reply->str, "PONG") == 0)
+        {
+            freeReplyObject(reply);
+            reply = NULL;
+            
+            dictReleaseIterator(di);            
+        
+            return node;
+        }
+        else if(reply != NULL)
+        {
+            freeReplyObject(reply);
+            reply = NULL;
+        }
+    }
+
+    dictReleaseIterator(di);
+
+    return NULL;
+}
+
+static int slot_get_by_command(redisClusterContext *cc, char *cmd, int len)
+{
+    struct cmd *command = NULL;
+    struct keypos *kp;
+    int key_count;
+    uint32_t i;
+    int slot_num = -1;
+
+    if(cc == NULL || cmd == NULL || len <= 0)
+    {
+        goto done;
+    }
+
+    command = command_get();
+    if(command == NULL)
+    {
+        __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory");
+        goto done;
+    }
+    
+    command->cmd = cmd;
+    command->clen = len;
+    redis_parse_cmd(command);
+    if(command->result != CMD_PARSE_OK)
+    {
+        __redisClusterSetError(cc, REDIS_ERR_PROTOCOL, "parse command error");
+        goto done;
+    }
+
+    key_count = hiarray_n(command->keys);
+
+    if(key_count <= 0)
+    {
+        __redisClusterSetError(cc, REDIS_ERR_OTHER, "no keys in command(must have keys for redis cluster mode)");
+        goto done;
+    }
+    else if(key_count == 1)
+    {
+        kp = hiarray_get(command->keys, 0);
+        slot_num = keyHashSlot(kp->start, kp->end - kp->start);
+
+        goto done;
+    }
+    
+    for(i = 0; i < hiarray_n(command->keys); i ++)
+    {
+        kp = hiarray_get(command->keys, i);
+
+        slot_num = keyHashSlot(kp->start, kp->end - kp->start);
+    }
+
+done:
+    
+    if(command != NULL)
+    {
+        command->cmd = NULL;
+        command_destroy(command);
+    }
+    
+    return slot_num;
+}
+
+/* Get the cluster config from one node.
+  * Return value: config_value string must free by usr.
+  */
+static char * cluster_config_get(redisClusterContext *cc, 
+    const char *config_name, int *config_value_len)
+{
+    redisContext *c;
+    cluster_node *node;
+    redisReply *reply = NULL, *sub_reply;
+    char *config_value = NULL;
+
+    if(cc == NULL || config_name == NULL
+        || config_value_len == NULL)
+    {
+        return NULL;
+    }
+    
+    node = node_get_witch_connected(cc);
+    if(node == NULL)
+    {
+        __redisClusterSetError(cc, 
+            REDIS_ERR_OTHER, "no reachable node in cluster");
+        goto error;
+    }
+
+    c = ctx_get_by_node(node, cc->timeout, cc->flags);
+    
+    reply = redisCommand(c, "config get %s", config_name);
+    if(reply == NULL)
+    {
+        __redisClusterSetError(cc, 
+            REDIS_ERR_OTHER, "reply for config get is null");
+        goto error;
+    }
+
+    if(reply->type != REDIS_REPLY_ARRAY)
+    {
+        __redisClusterSetError(cc, REDIS_ERR_OTHER, 
+            "reply for config get type is not array");
+        goto error;
+    }
+
+    if(reply->elements != 2)
+    {
+        __redisClusterSetError(cc, REDIS_ERR_OTHER, 
+            "reply for config get elements number is not 2");
+        goto error;
+    }
+
+    sub_reply = reply->element[0];
+    if(sub_reply == NULL || sub_reply->type != REDIS_REPLY_STRING)
+    {
+        __redisClusterSetError(cc, REDIS_ERR_OTHER, 
+            "reply for config get config name is not string");
+        goto error;
+    }
+
+    if(strcmp(sub_reply->str, config_name))
+    {
+        __redisClusterSetError(cc, REDIS_ERR_OTHER, 
+            "reply for config get config name is not we want");
+        goto error;
+    }
+
+    sub_reply = reply->element[1];
+    if(sub_reply == NULL || sub_reply->type != REDIS_REPLY_STRING)
+    {
+        __redisClusterSetError(cc, REDIS_ERR_OTHER, 
+            "reply for config get config value type is not string");
+        goto error;
+    }
+
+    config_value = sub_reply->str;
+    *config_value_len = sub_reply->len;
+    sub_reply->str= NULL;
+
+    if(reply != NULL)
+    {
+        freeReplyObject(reply);    
+    }
+
+    return config_value;
+
+error:
+
+    if(reply != NULL)
+    {
+        freeReplyObject(reply);    
+    }
+
+    return NULL;
+}
+
+/* Helper function for the redisClusterAppendCommand* family of functions.
+ *
+ * Write a formatted command to the output buffer. When this family
+ * is used, you need to call redisGetReply yourself to retrieve
+ * the reply (or replies in pub/sub).
+ */
+static int __redisClusterAppendCommand(redisClusterContext *cc, 
+    struct cmd *command) {
+
+    cluster_node *node;
+    redisContext *c = NULL;
+
+    if(cc == NULL || command == NULL)
+    {
+        return REDIS_ERR;
+    }
+    
+    node = node_get_by_table(cc, (uint32_t)command->slot_num);
+    if(node == NULL)
+    {
+        __redisClusterSetError(cc, REDIS_ERR_OTHER, "node get by slot error");
+        return REDIS_ERR;
+    }
+
+    c = ctx_get_by_node(node, cc->timeout, cc->flags);
+    if(c == NULL)
+    {
+        __redisClusterSetError(cc, REDIS_ERR_OTHER, "ctx get by node is null");
+        return REDIS_ERR;
+    }
+    else if(c->err)
+    {
+        __redisClusterSetError(cc, c->err, c->errstr);
+        return REDIS_ERR;
+    }
+
+    if (__redisAppendCommand(c, command->cmd, command->clen) != REDIS_OK) 
+    {
+        __redisClusterSetError(cc, c->err, c->errstr);
+        return REDIS_ERR;
+    }
+    
+    return REDIS_OK;
+}
+
+/* Helper function for the redisClusterGetReply* family of functions.
+ */
+static int __redisClusterGetReply(redisClusterContext *cc, int slot_num, void **reply)
+{
+    cluster_node *node;
+    redisContext *c;
+
+    if(cc == NULL || slot_num < 0 || reply == NULL)
+    {
+        return REDIS_ERR;
+    }
+
+    node = node_get_by_table(cc, (uint32_t)slot_num);
+    if(node == NULL)
+    {
+        __redisClusterSetError(cc, REDIS_ERR_OTHER, "node get by table is null");
+        return REDIS_ERR;
+    }
+
+    c = ctx_get_by_node(node, cc->timeout, cc->flags);
+    if(c == NULL)
+    {
+        __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory");
+        return REDIS_ERR;
+    }
+    else if(c->err)
+    {
+        if(cc->need_update_route == 0)
+        {
+            cc->retry_count ++;
+            if(cc->retry_count > cc->max_redirect_count)
+            {
+                cc->need_update_route = 1;
+                cc->retry_count = 0;
+            }
+        }
+        __redisClusterSetError(cc, c->err, c->errstr);
+        return REDIS_ERR;
+    }
+
+    if(redisGetReply(c, reply) != REDIS_OK)
+    {
+        __redisClusterSetError(cc, c->err, c->errstr);
+        return REDIS_ERR;
+    }
+    
+    if(cluster_reply_error_type(*reply) == CLUSTER_ERR_MOVED)
+    {
+        cc->need_update_route = 1;
+    }
+
+    return REDIS_OK;
+}
+
+static cluster_node *node_get_by_ask_error_reply(
+    redisClusterContext *cc, redisReply *reply)
+{
+    sds *part = NULL, *ip_port = NULL;
+    int part_len = 0, ip_port_len;
+    dictEntry *de;
+    cluster_node *node = NULL;
+
+    if(cc == NULL || reply == NULL)
+    {
+        return NULL;
+    }
+
+    if(cluster_reply_error_type(reply) != CLUSTER_ERR_ASK)
+    {
+        __redisClusterSetError(cc, REDIS_ERR_OTHER, 
+            "reply is not ask error!");
+        return NULL;
+    }
+    
+    part = sdssplitlen(reply->str, reply->len, " ", 1, &part_len);
+
+    if(part != NULL && part_len == 3)
+    {
+        ip_port = sdssplitlen(part[2], sdslen(part[2]), 
+            ":", 1, &ip_port_len);
+
+        if(ip_port != NULL && ip_port_len == 2)
+        {
+            de = dictFind(cc->nodes, part[2]);
+            if(de == NULL)
+            {
+                node = hi_alloc(sizeof(cluster_node));
+                if(node == NULL)
+                {
+                    __redisClusterSetError(cc, 
+                        REDIS_ERR_OOM, "Out of memory");
+
+                    goto done;
+                }
+
+                cluster_node_init(node);
+                node->addr = part[1];
+                node->host = ip_port[0];
+                node->port = hi_atoi(ip_port[1], sdslen(ip_port[1]));
+                node->role = REDIS_ROLE_MASTER;
+
+                dictAdd(cc->nodes, sdsnewlen(node->addr, sdslen(node->addr)), node);
+                
+                part = NULL;
+                ip_port = NULL;
+            }
+            else
+            {
+                node = de->val;
+
+                goto done;
+            }
+        }
+        else
+        {
+            __redisClusterSetError(cc, REDIS_ERR_OTHER, 
+                "ask error reply address part parse error!");
+
+            goto done;
+        }
+
+    }
+    else
+    {
+        __redisClusterSetError(cc, REDIS_ERR_OTHER, 
+            "ask error reply parse error!");
+
+        goto done;
+    }
+
+done:
+
+    if(part != NULL)
+    {
+        sdsfreesplitres(part, part_len);
+        part = NULL;
+    }
+
+    if(ip_port != NULL)
+    {
+        sdsfreesplitres(ip_port, ip_port_len);
+        ip_port = NULL;
+    }
+    
+    return node;
+}
+
+static void *redis_cluster_command_execute(redisClusterContext *cc, 
+    struct cmd *command)
+{
+    int ret;
+    void *reply = NULL;
+    cluster_node *node;
+    redisContext *c = NULL;
+    int error_type;
+
+retry:
+    
+    node = node_get_by_table(cc, (uint32_t)command->slot_num);
+    if(node == NULL)
+    {
+        __redisClusterSetError(cc, REDIS_ERR_OTHER, "node get by table error");
+        return NULL;
+    }
+
+    c = ctx_get_by_node(node, cc->timeout, cc->flags);
+    if(c == NULL)
+    {
+        __redisClusterSetError(cc, REDIS_ERR_OTHER, "ctx get by node is null");
+        return NULL;
+    }
+    else if(c->err)
+    {
+        node = node_get_witch_connected(cc);
+        if(node == NULL)
+        {
+            __redisClusterSetError(cc, REDIS_ERR_OTHER, "no reachable node in cluster");
+            return NULL;
+        }
+
+        cc->retry_count ++;
+        if(cc->retry_count > cc->max_redirect_count)
+        {
+            __redisClusterSetError(cc, REDIS_ERR_CLUSTER_TOO_MANY_REDIRECT, 
+                "too many cluster redirect");
+            return NULL;
+        }
+
+        c = ctx_get_by_node(node, cc->timeout, cc->flags);
+        if(c == NULL)
+        {
+            __redisClusterSetError(cc, REDIS_ERR_OTHER, "ctx get by node error");
+            return NULL;
+        }
+        else if(c->err)
+        {
+            __redisClusterSetError(cc, c->err, c->errstr);
+            return NULL;
+        }
+    }
+
+ask_retry:
+
+    if (__redisAppendCommand(c,command->cmd, command->clen) != REDIS_OK) 
+    {
+        __redisClusterSetError(cc, c->err, c->errstr);
+        return NULL;
+    }
+    
+    reply = __redisBlockForReply(c);
+    if(reply == NULL)
+    {
+        __redisClusterSetError(cc, c->err, c->errstr);
+        return NULL;
+    }
+
+    error_type = cluster_reply_error_type(reply);
+    if(error_type > CLUSTER_NOT_ERR && error_type < CLUSTER_ERR_SENTINEL)
+    {
+        cc->retry_count ++;
+        if(cc->retry_count > cc->max_redirect_count)
+        {
+            __redisClusterSetError(cc, REDIS_ERR_CLUSTER_TOO_MANY_REDIRECT, 
+                "too many cluster redirect");
+            freeReplyObject(reply);
+            return NULL;
+        }
+        
+        switch(error_type)
+        {
+        case CLUSTER_ERR_MOVED:
+            freeReplyObject(reply);
+            reply = NULL;
+            ret = cluster_update_route(cc);
+            if(ret != REDIS_OK)
+            {
+                __redisClusterSetError(cc, REDIS_ERR_OTHER, 
+                    "route update error, please recreate redisClusterContext!");
+                return NULL;
+            }
+            
+            goto retry;
+            
+            break;
+        case CLUSTER_ERR_ASK:
+            node = node_get_by_ask_error_reply(cc, reply);
+            if(node == NULL)
+            {
+                freeReplyObject(reply);
+                return NULL;
+            }
+
+            freeReplyObject(reply);
+            reply = NULL;
+
+            c = ctx_get_by_node(node, cc->timeout, cc->flags);
+            if(c == NULL)
+            {
+                __redisClusterSetError(cc, REDIS_ERR_OTHER, "ctx get by node error");
+                return NULL;
+            }
+            else if(c->err)
+            {
+                __redisClusterSetError(cc, c->err, c->errstr);
+                return NULL;
+            }
+
+            reply = redisCommand(c, REDIS_COMMAND_ASKING);
+            if(reply == NULL)
+            {
+                __redisClusterSetError(cc, c->err, c->errstr);
+                return NULL;
+            }
+
+            freeReplyObject(reply);
+            reply = NULL;
+            
+            goto ask_retry;
+
+            break;
+        case CLUSTER_ERR_TRYAGAIN:
+        case CLUSTER_ERR_CROSSSLOT:
+        case CLUSTER_ERR_CLUSTERDOWN:
+            freeReplyObject(reply);
+            reply = NULL;
+            goto retry;
+            
+            break;
+        default:
+
+            break;
+        }
+    }
+    
+    return reply;
+}
+
+static int command_pre_fragment(redisClusterContext *cc, 
+    struct cmd *command, hilist *commands)
+{
+    
+    struct keypos *kp, *sub_kp;
+    uint32_t key_count;
+    uint32_t i, j;
+    uint32_t idx;
+    uint32_t key_len;
+    int slot_num = -1;
+    struct cmd *sub_command;
+    struct cmd **sub_commands = NULL;
+    char num_str[12];
+    uint8_t num_str_len;
+    
+
+    if(command == NULL || commands == NULL)
+    {
+        goto done;
+    }
+
+    key_count = hiarray_n(command->keys);
+
+    sub_commands = hi_zalloc(REDIS_CLUSTER_SLOTS * sizeof(*sub_commands));
+    if (sub_commands == NULL) 
+    {
+        __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory");
+        goto done;
+    }
+
+    command->frag_seq = hi_alloc(key_count * sizeof(*command->frag_seq));
+    if(command->frag_seq == NULL)
+    {
+        __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory");
+        goto done;
+    }
+    
+    
+    for(i = 0; i < key_count; i ++)
+    {
+        kp = hiarray_get(command->keys, i);
+
+        slot_num = keyHashSlot(kp->start, kp->end - kp->start);
+
+        if(slot_num < 0 || slot_num >= REDIS_CLUSTER_SLOTS)
+        {
+            __redisClusterSetError(cc,REDIS_ERR_OTHER,"keyHashSlot return error");
+            goto done;
+        }
+
+        if (sub_commands[slot_num] == NULL) {
+            sub_commands[slot_num] = command_get();
+            if (sub_commands[slot_num] == NULL) {
+                __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory");
+                slot_num = -1;
+                goto done;
+            }
+        }
+
+        command->frag_seq[i] = sub_command = sub_commands[slot_num];
+
+        sub_command->narg++;
+
+        sub_kp = hiarray_push(sub_command->keys);
+        if (sub_kp == NULL) {
+            __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory");
+            slot_num = -1;
+            goto done;
+        }
+        
+        sub_kp->start = kp->start;
+        sub_kp->end = kp->end;
+
+        key_len = (uint32_t)(kp->end - kp->start);
+
+        sub_command->clen += key_len + uint_len(key_len);
+
+        sub_command->slot_num = slot_num;
+
+        if (command->type == CMD_REQ_REDIS_MSET) {
+            uint32_t len = 0;
+            char *p;
+
+            for (p = sub_kp->end + 1; !isdigit(*p); p++){}
+            
+            p = sub_kp->end + 1;
+            while(!isdigit(*p))
+            {
+                p ++;
+            }
+
+            for (; isdigit(*p); p++) {              
+                len = len * 10 + (uint32_t)(*p - '0');
+            }
+            
+            len += CRLF_LEN * 2;
+            len += (p - sub_kp->end);
+            sub_kp->remain_len = len;
+            sub_command->clen += len;
+        }
+    }
+
+    for (i = 0; i < REDIS_CLUSTER_SLOTS; i++) {     /* prepend command header */
+        sub_command = sub_commands[i];
+        if (sub_command == NULL) {
+            continue;
+        }
+
+        idx = 0;            
+        if (command->type == CMD_REQ_REDIS_MGET) {
+            //"*%d\r\n$4\r\nmget\r\n"
+            
+            sub_command->clen += 5*sub_command->narg;
+
+            sub_command->narg ++;
+
+            hi_itoa(num_str, sub_command->narg);
+            num_str_len = (uint8_t)(strlen(num_str));
+
+            sub_command->clen += 13 + num_str_len;
+
+            sub_command->cmd = hi_zalloc(sub_command->clen * sizeof(*sub_command->cmd));
+            if(sub_command->cmd == NULL)
+            {
+                __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory");
+                slot_num = -1;
+                goto done;
+            }
+
+            sub_command->cmd[idx++] = '*';
+            memcpy(sub_command->cmd + idx, num_str, num_str_len);
+            idx += num_str_len;
+            memcpy(sub_command->cmd + idx, "\r\n$4\r\nmget\r\n", 12);
+            idx += 12;
+            
+            for(j = 0; j < hiarray_n(sub_command->keys); j ++)
+            {
+                kp = hiarray_get(sub_command->keys, j);
+                key_len = (uint32_t)(kp->end - kp->start);
+                hi_itoa(num_str, key_len);
+                num_str_len = strlen(num_str);
+
+                sub_command->cmd[idx++] = '$';
+                memcpy(sub_command->cmd + idx, num_str, num_str_len);
+                idx += num_str_len;
+                memcpy(sub_command->cmd + idx, CRLF, CRLF_LEN);
+                idx += CRLF_LEN;
+                memcpy(sub_command->cmd + idx, kp->start, key_len);
+                idx += key_len;
+                memcpy(sub_command->cmd + idx, CRLF, CRLF_LEN);
+                idx += CRLF_LEN;
+            }
+        } else if (command->type == CMD_REQ_REDIS_DEL) {
+            //"*%d\r\n$3\r\ndel\r\n"
+            
+            sub_command->clen += 5*sub_command->narg;
+
+            sub_command->narg ++;
+
+            hi_itoa(num_str, sub_command->narg);
+            num_str_len = (uint8_t)strlen(num_str);
+            
+            sub_command->clen += 12 + num_str_len;
+
+            sub_command->cmd = hi_zalloc(sub_command->clen * sizeof(*sub_command->cmd));
+            if(sub_command->cmd == NULL)
+            {
+                __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory");
+                slot_num = -1;
+                goto done;
+            }
+
+            sub_command->cmd[idx++] = '*';
+            memcpy(sub_command->cmd + idx, num_str, num_str_len);
+            idx += num_str_len;
+            memcpy(sub_command->cmd + idx, "\r\n$3\r\ndel\r\n", 11);
+            idx += 11;
+
+            for(j = 0; j < hiarray_n(sub_command->keys); j ++)
+            {
+                kp = hiarray_get(sub_command->keys, j);
+                key_len = (uint32_t)(kp->end - kp->start);
+                hi_itoa(num_str, key_len);
+                num_str_len = strlen(num_str);
+
+                sub_command->cmd[idx++] = '$';
+                memcpy(sub_command->cmd + idx, num_str, num_str_len);
+                idx += num_str_len;
+                memcpy(sub_command->cmd + idx, CRLF, CRLF_LEN);
+                idx += CRLF_LEN;
+                memcpy(sub_command->cmd + idx, kp->start, key_len);
+                idx += key_len;
+                memcpy(sub_command->cmd + idx, CRLF, CRLF_LEN);
+                idx += CRLF_LEN;
+            }
+        } else if (command->type == CMD_REQ_REDIS_MSET) {
+            //"*%d\r\n$4\r\nmset\r\n"
+            
+            sub_command->clen += 3*sub_command->narg;
+
+            sub_command->narg *= 2;
+
+            sub_command->narg ++;
+
+            hi_itoa(num_str, sub_command->narg);
+            num_str_len = (uint8_t)strlen(num_str);
+        
+            sub_command->clen += 13 + num_str_len;
+
+            sub_command->cmd = hi_zalloc(sub_command->clen * sizeof(*sub_command->cmd));
+            if(sub_command->cmd == NULL)
+            {
+                __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory");
+                slot_num = -1;
+                goto done;
+            }
+
+            sub_command->cmd[idx++] = '*';
+            memcpy(sub_command->cmd + idx, num_str, num_str_len);
+            idx += num_str_len;
+            memcpy(sub_command->cmd + idx, "\r\n$4\r\nmset\r\n", 12);
+            idx += 12;
+            
+            for(j = 0; j < hiarray_n(sub_command->keys); j ++)
+            {
+                kp = hiarray_get(sub_command->keys, j);
+                key_len = (uint32_t)(kp->end - kp->start);
+                hi_itoa(num_str, key_len);
+                num_str_len = strlen(num_str);
+
+                sub_command->cmd[idx++] = '$';
+                memcpy(sub_command->cmd + idx, num_str, num_str_len);
+                idx += num_str_len;
+                memcpy(sub_command->cmd + idx, CRLF, CRLF_LEN);
+                idx += CRLF_LEN;
+                memcpy(sub_command->cmd + idx, kp->start, key_len + kp->remain_len);
+                idx += key_len + kp->remain_len;
+                
+            }
+        } else {
+            NOT_REACHED();
+        }
+
+        //printf("len : %d\n", sub_command->clen);
+        //print_string_with_length_fix_CRLF(sub_command->cmd, sub_command->clen);
+        
+        sub_command->type = command->type;
+
+        listAddNodeTail(commands, sub_command);
+    }
+
+done:
+
+    if(sub_commands != NULL)
+    {
+        hi_free(sub_commands);
+    }
+
+    if(slot_num >= 0 && commands != NULL 
+        && listLength(commands) == 1)
+    {
+        listNode *list_node = listFirst(commands);
+        listDelNode(commands, list_node);
+        if(command->frag_seq)
+        {
+            hi_free(command->frag_seq);
+            command->frag_seq = NULL;
+        }
+
+        command->slot_num = slot_num;
+    }
+
+    return slot_num;
+}
+
+static void *command_post_fragment(redisClusterContext *cc, 
+    struct cmd *command, hilist *commands)
+{
+    struct cmd *sub_command;
+    listNode *list_node;
+    listIter *list_iter;
+    redisReply *reply, *sub_reply;
+    long long count = 0;
+    
+    list_iter = listGetIterator(commands, AL_START_HEAD);
+    while((list_node = listNext(list_iter)) != NULL)
+    {
+        sub_command = list_node->value;
+        reply = sub_command->reply;
+        if(reply == NULL)
+        {
+            return NULL;
+        }
+        else if(reply->type == REDIS_REPLY_ERROR)
+        {
+            return reply;
+        }
+
+        if (command->type == CMD_REQ_REDIS_MGET) {
+            if(reply->type != REDIS_REPLY_ARRAY)
+            {
+                __redisClusterSetError(cc,REDIS_ERR_OTHER,"reply type is error(here only can be array)");
+                return NULL;
+            }
+        }else if(command->type == CMD_REQ_REDIS_DEL){
+            if(reply->type != REDIS_REPLY_INTEGER)
+            {
+                __redisClusterSetError(cc,REDIS_ERR_OTHER,"reply type is error(here only can be integer)");
+                return NULL;
+            }
+
+            count += reply->integer;
+        }else if(command->type == CMD_REQ_REDIS_MSET){
+            if(reply->type != REDIS_REPLY_STATUS ||
+                reply->len != 2 || strcmp(reply->str, REDIS_STATUS_OK) != 0)
+            {
+                __redisClusterSetError(cc,REDIS_ERR_OTHER,"reply type is error(here only can be status and ok)");
+                return NULL;
+            }
+        }else {
+            NOT_REACHED();
+        }
+    }
+
+    reply = hi_calloc(1,sizeof(*reply));
+
+    if (reply == NULL)
+    {
+        __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory");
+        return NULL;
+    }
+
+    if (command->type == CMD_REQ_REDIS_MGET) {
+        int i;
+        uint32_t key_count;
+
+        reply->type = REDIS_REPLY_ARRAY;
+
+        key_count = hiarray_n(command->keys);
+
+        reply->elements = key_count;
+        reply->element = hi_calloc(key_count, sizeof(*reply));
+        if (reply->element == NULL) {
+            freeReplyObject(reply);
+            __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory");
+            return NULL;
+        }
+            
+        for (i = key_count - 1; i >= 0; i--) {      /* for each key */
+            sub_reply = command->frag_seq[i]->reply;            /* get it's reply */
+            if (sub_reply == NULL) {
+                freeReplyObject(reply);
+                __redisClusterSetError(cc,REDIS_ERR_OTHER,"sub reply is null");
+                return NULL;
+            }
+
+            if(sub_reply->type == REDIS_REPLY_STRING)
+            {
+                reply->element[i] = sub_reply;
+            }
+            else if(sub_reply->type == REDIS_REPLY_ARRAY)
+            {
+                if(sub_reply->elements == 0)
+                {
+                    freeReplyObject(reply);
+                    __redisClusterSetError(cc,REDIS_ERR_OTHER,"sub reply elements error");
+                    return NULL;
+                }
+                
+                reply->element[i] = sub_reply->element[sub_reply->elements - 1];
+                sub_reply->elements --;
+            }
+        }
+    }else if(command->type == CMD_REQ_REDIS_DEL){
+        reply->type = REDIS_REPLY_INTEGER;
+        reply->integer = count;
+    }else if(command->type == CMD_REQ_REDIS_MSET){
+        reply->type = REDIS_REPLY_STATUS;
+        uint32_t str_len = strlen(REDIS_STATUS_OK);
+        reply->str = hi_alloc((str_len + 1) * sizeof(char*));
+        if(reply->str == NULL)
+        {
+            freeReplyObject(reply);
+            __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory");
+            return NULL;
+        }
+
+        reply->len = str_len;
+        memcpy(reply->str, REDIS_STATUS_OK, str_len);
+        reply->str[str_len] = '\0';
+    }else {
+        NOT_REACHED();
+    }
+
+    return reply;
+}
+
+/* 
+ * Split the command into subcommands by slot
+ * 
+ * Returns slot_num
+ * If slot_num < 0 or slot_num >=  REDIS_CLUSTER_SLOTS means this function runs error;
+ * Otherwise if  the commands > 1 , slot_num is the last subcommand slot number. 
+ */
+static int command_format_by_slot(redisClusterContext *cc, 
+    struct cmd *command, hilist *commands)
+{
+    struct keypos *kp;
+    int key_count;
+    int slot_num = -1;
+
+    if(cc == NULL || commands == NULL ||
+        command == NULL || 
+        command->cmd == NULL || command->clen <= 0)
+    {
+        goto done;
+    }
+
+    
+    redis_parse_cmd(command);
+    if(command->result == CMD_PARSE_ENOMEM)
+    {
+        __redisClusterSetError(cc, REDIS_ERR_PROTOCOL, "Parse command error: out of memory");
+        goto done;
+    }
+    else if(command->result != CMD_PARSE_OK)
+    {
+        __redisClusterSetError(cc, REDIS_ERR_PROTOCOL, command->errstr);
+        goto done;
+    }
+
+    key_count = hiarray_n(command->keys);
+
+    if(key_count <= 0)
+    {
+        __redisClusterSetError(cc, REDIS_ERR_OTHER, "No keys in command(must have keys for redis cluster mode)");
+        goto done;
+    }
+    else if(key_count == 1)
+    {
+        kp = hiarray_get(command->keys, 0);
+        slot_num = keyHashSlot(kp->start, kp->end - kp->start);
+        command->slot_num = slot_num;
+
+        goto done;
+    }
+
+    slot_num = command_pre_fragment(cc, command, commands);
+
+done:
+    
+    return slot_num;
+}
+
+
+void redisClusterSetMaxRedirect(redisClusterContext *cc, int max_redirect_count)
+{
+    if(cc == NULL || max_redirect_count <= 0)
+    {
+        return;
+    }
+
+    cc->max_redirect_count = max_redirect_count;
+}
+
+void *redisClusterFormattedCommand(redisClusterContext *cc, char *cmd, int len) {
+    redisReply *reply = NULL;
+    int slot_num;
+    struct cmd *command = NULL, *sub_command;
+    hilist *commands = NULL;
+    listNode *list_node;
+    listIter *list_iter = NULL;
+
+    if(cc == NULL)
+    {
+        return NULL;
+    }
+
+    if(cc->err)
+    {
+        cc->err = 0;
+        memset(cc->errstr, '\0', strlen(cc->errstr));
+    }  
+    
+    command = command_get();
+    if(command == NULL)
+    {
+        __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory");
+        return NULL;
+    }
+    
+    command->cmd = cmd;
+    command->clen = len;
+
+    commands = listCreate();
+    if(commands == NULL)
+    {
+        __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory");
+        goto error;
+    }
+
+    commands->free = listCommandFree;
+
+    slot_num = command_format_by_slot(cc, command, commands);
+
+    if(slot_num < 0)
+    {
+        goto error;
+    }
+    else if(slot_num >= REDIS_CLUSTER_SLOTS)
+    {
+        __redisClusterSetError(cc,REDIS_ERR_OTHER,"slot_num is out of range");
+        goto error;
+    }
+
+    //all keys belong to one slot
+    if(listLength(commands) == 0)
+    {
+        reply = redis_cluster_command_execute(cc, command);
+        goto done;
+    }
+
+    ASSERT(listLength(commands) != 1);
+
+    list_iter = listGetIterator(commands, AL_START_HEAD);
+    while((list_node = listNext(list_iter)) != NULL)
+    {
+        sub_command = list_node->value;
+        
+        reply = redis_cluster_command_execute(cc, sub_command);
+        if(reply == NULL)
+        {
+            goto error;
+        }
+        else if(reply->type == REDIS_REPLY_ERROR)
+        {
+            goto done;
+        }
+
+        sub_command->reply = reply;
+    }
+
+    reply = command_post_fragment(cc, command, commands);
+    
+done:
+
+    command->cmd = NULL;
+    command_destroy(command);
+
+    if(commands != NULL)
+    {
+        listRelease(commands);
+    }
+
+    if(list_iter != NULL)
+    {
+        listReleaseIterator(list_iter);
+    }
+
+    cc->retry_count = 0;
+    
+    return reply;
+
+error:
+
+    if(command != NULL)
+    {
+        command->cmd = NULL;
+        command_destroy(command);
+    }
+
+    if(commands != NULL)
+    {
+        listRelease(commands);
+    }
+
+    if(list_iter != NULL)
+    {
+        listReleaseIterator(list_iter);
+    }
+
+    cc->retry_count = 0;
+    
+    return NULL;
+}
+
+void *redisClustervCommand(redisClusterContext *cc, const char *format, va_list ap) {
+    redisReply *reply;
+    char *cmd;
+    int len;
+
+    if(cc == NULL)
+    {
+        return NULL;
+    }
+
+    len = redisvFormatCommand(&cmd,format,ap);
+
+    if (len == -1) {
+        __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory");
+        return NULL;
+    } else if (len == -2) {
+        __redisClusterSetError(cc,REDIS_ERR_OTHER,"Invalid format string");
+        return NULL;
+    }   
+
+    reply = redisClusterFormattedCommand(cc, cmd, len);
+
+    free(cmd);
+
+    return reply;
+}
+
+void *redisClusterCommand(redisClusterContext *cc, const char *format, ...) {
+    va_list ap;
+    redisReply *reply = NULL;
+    
+    va_start(ap,format);
+    reply = redisClustervCommand(cc, format, ap);
+    va_end(ap);
+
+    return reply;
+}
+
+void *redisClusterCommandArgv(redisClusterContext *cc, int argc, const char **argv, const size_t *argvlen) {
+    redisReply *reply = NULL;
+    char *cmd;
+    int len;
+
+    len = redisFormatCommandArgv(&cmd,argc,argv,argvlen);
+    if (len == -1) {
+        __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory");
+        return NULL;
+    }
+	
+    reply = redisClusterFormattedCommand(cc, cmd, len);
+
+    free(cmd);
+
+    return reply;
+}
+
+int redisClusterAppendFormattedCommand(redisClusterContext *cc, 
+    char *cmd, int len) {
+    int slot_num;
+    struct cmd *command = NULL, *sub_command;
+    hilist *commands = NULL;
+    listNode *list_node;
+    listIter *list_iter = NULL;
+
+    if(cc->requests == NULL)
+    {
+        cc->requests = listCreate();
+        if(cc->requests == NULL)
+        {
+            __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory");
+            goto error;
+        }
+
+        cc->requests->free = listCommandFree;
+    }
+    
+    command = command_get();
+    if(command == NULL)
+    {
+        __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory");
+        goto error;
+    }
+    
+    command->cmd = cmd;
+    command->clen = len;
+
+    commands = listCreate();
+    if(commands == NULL)
+    {
+        __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory");
+        goto error;
+    }
+
+    commands->free = listCommandFree;
+
+    slot_num = command_format_by_slot(cc, command, commands);
+
+    if(slot_num < 0)
+    {
+        goto error;
+    }
+    else if(slot_num >= REDIS_CLUSTER_SLOTS)
+    {
+        __redisClusterSetError(cc,REDIS_ERR_OTHER,"slot_num is out of range");
+        goto error;
+    }
+
+    //all keys belong to one slot
+    if(listLength(commands) == 0)
+    {
+        if(__redisClusterAppendCommand(cc, command) == REDIS_OK)
+        {
+            goto done;
+        }
+        else
+        {
+            goto error;
+        }
+    }
+
+    ASSERT(listLength(commands) != 1);
+
+    list_iter = listGetIterator(commands, AL_START_HEAD);
+    while((list_node = listNext(list_iter)) != NULL)
+    {
+        sub_command = list_node->value;
+        
+        if(__redisClusterAppendCommand(cc, sub_command) == REDIS_OK)
+        {
+            continue;
+        }
+        else
+        {
+            goto error;
+        }
+    }
+
+done:
+
+    if(command->cmd != NULL)
+    {
+        command->cmd = NULL;
+    }
+    else
+    {
+        goto error;
+    }
+
+    if(commands != NULL)
+    {
+        if(listLength(commands) > 0)
+        {
+            command->sub_commands = commands;
+        }
+        else
+        {
+            listRelease(commands);
+        }
+    }
+
+    if(list_iter != NULL)
+    {
+        listReleaseIterator(list_iter);
+    }
+
+    listAddNodeTail(cc->requests, command);
+    
+    return REDIS_OK;
+
+error:
+
+    if(command != NULL)
+    {
+        command->cmd = NULL;
+        command_destroy(command);
+    }
+
+    if(commands != NULL)
+    {
+        listRelease(commands);
+    }
+
+    if(list_iter != NULL)
+    {
+        listReleaseIterator(list_iter);
+    }
+
+    /* Attention: mybe here we must pop the 
+      sub_commands that had append to the nodes.  
+      But now we do not handle it. */
+    
+    return REDIS_ERR;
+}
+
+
+int redisClustervAppendCommand(redisClusterContext *cc, 
+    const char *format, va_list ap) {
+    int ret;
+    char *cmd;
+    int len;
+    
+    len = redisvFormatCommand(&cmd,format,ap);  
+    if (len == -1) {
+        __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory");
+        return REDIS_ERR;
+    } else if (len == -2) {
+        __redisClusterSetError(cc,REDIS_ERR_OTHER,"Invalid format string");
+        return REDIS_ERR;
+    }   
+
+    ret = redisClusterAppendFormattedCommand(cc, cmd, len);
+
+    free(cmd);
+
+    return ret;
+}
+
+int redisClusterAppendCommand(redisClusterContext *cc, 
+    const char *format, ...) {
+
+    int ret;
+    va_list ap;
+
+    if(cc == NULL || format == NULL)
+    {
+        return REDIS_ERR;
+    }
+    
+    va_start(ap,format);
+    ret = redisClustervAppendCommand(cc, format, ap);
+    va_end(ap);
+
+    return ret;
+}
+
+int redisClusterAppendCommandArgv(redisClusterContext *cc, 
+    int argc, const char **argv, const size_t *argvlen) {
+    int ret;
+    char *cmd;
+    int len;
+
+    len = redisFormatCommandArgv(&cmd,argc,argv,argvlen);
+    if (len == -1) {
+        __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory");
+        return REDIS_ERR;
+    }
+    
+    ret = redisClusterAppendFormattedCommand(cc, cmd, len);
+    
+    free(cmd);
+
+    return ret;
+}
+
+static int redisCLusterSendAll(redisClusterContext *cc)
+{
+    dictIterator *di;
+    dictEntry *de;
+    struct cluster_node *node;
+    redisContext *c = NULL;
+    int wdone = 0;
+    
+    if(cc == NULL || cc->nodes == NULL)
+    {
+        return REDIS_ERR;
+    }
+
+    di = dictGetIterator(cc->nodes);
+    while((de = dictNext(di)) != NULL)
+    {
+        node = dictGetEntryVal(de);
+        if(node == NULL)
+        {
+            continue;
+        }
+        
+        c = ctx_get_by_node(node, cc->timeout, cc->flags);
+        if(c == NULL)
+        {
+            continue;
+        }
+
+        if (c->flags & REDIS_BLOCK) {
+            /* Write until done */
+            do {
+                if (redisBufferWrite(c,&wdone) == REDIS_ERR)
+                {
+                    dictReleaseIterator(di);
+                    return REDIS_ERR;
+                }
+            } while (!wdone);
+        }
+    }
+    
+    dictReleaseIterator(di);
+
+    return REDIS_OK;
+}
+
+static int redisCLusterClearAll(redisClusterContext *cc)
+{
+    dictIterator *di;
+    dictEntry *de;
+    struct cluster_node *node;
+    redisContext *c = NULL;
+    
+    if (cc == NULL) {
+        return REDIS_ERR;
+    }
+
+    if (cc->err) {
+        cc->err = 0;
+        memset(cc->errstr, '\0', strlen(cc->errstr));
+    }
+
+    if (cc->nodes == NULL) {
+        return REDIS_ERR;
+    }
+    di = dictGetIterator(cc->nodes);
+    while((de = dictNext(di)) != NULL)
+    {
+        node = dictGetEntryVal(de);
+        if(node == NULL)
+        {
+            continue;
+        }
+
+        c = node->con;
+        if(c == NULL)
+        {
+            continue;
+        }
+
+        redisFree(c);
+        node->con = NULL;
+    }
+    
+    dictReleaseIterator(di);
+    
+    return REDIS_OK;
+}
+
+int redisClusterGetReply(redisClusterContext *cc, void **reply) {
+
+    struct cmd *command, *sub_command;
+    hilist *commands = NULL;
+    listNode *list_command, *list_sub_command;
+    listIter *list_iter;
+    int slot_num;
+    void *sub_reply;
+
+    if(cc == NULL || reply == NULL)
+        return REDIS_ERR;
+
+    cc->err = 0;
+    cc->errstr[0] = '\0';
+
+    *reply = NULL;
+
+    if (cc->requests == NULL)
+        return REDIS_ERR;
+
+    list_command = listFirst(cc->requests);
+
+    //no more reply
+    if(list_command == NULL)
+    {
+        *reply = NULL;
+        return REDIS_OK;
+    }
+    
+    command = list_command->value;
+    if(command == NULL)
+    {
+        __redisClusterSetError(cc,REDIS_ERR_OTHER,
+            "command in the requests list is null");
+        goto error;
+    }
+    
+    slot_num = command->slot_num;
+    if(slot_num >= 0)
+    {
+        listDelNode(cc->requests, list_command);
+        return __redisClusterGetReply(cc, slot_num, reply);
+    }
+
+    commands = command->sub_commands;
+    if(commands == NULL)
+    {
+        __redisClusterSetError(cc,REDIS_ERR_OTHER,
+            "sub_commands in command is null");
+        goto error;
+    }
+
+    ASSERT(listLength(commands) != 1);
+
+    list_iter = listGetIterator(commands, AL_START_HEAD);
+    while((list_sub_command = listNext(list_iter)) != NULL)
+    {
+        sub_command = list_sub_command->value;
+        if(sub_command == NULL)
+        {
+            __redisClusterSetError(cc,REDIS_ERR_OTHER,
+                "sub_command is null");
+            goto error;
+        }
+        
+        slot_num = sub_command->slot_num;
+        if(slot_num < 0)
+        {
+            __redisClusterSetError(cc,REDIS_ERR_OTHER,
+                "sub_command slot_num is less then zero");
+            goto error;
+        }
+        
+        if(__redisClusterGetReply(cc, slot_num, &sub_reply) != REDIS_OK)
+        {
+            goto error;
+        }
+
+        sub_command->reply = sub_reply;
+    }
+
+    *reply = command_post_fragment(cc, command, commands);
+    if(*reply == NULL)
+    {
+        goto error;
+    }
+
+    listDelNode(cc->requests, list_command);
+    return REDIS_OK;
+
+error:
+
+    listDelNode(cc->requests, list_command);
+    return REDIS_ERR;
+}
+
+void redisClusterReset(redisClusterContext *cc)
+{
+    int status;
+    void *reply;
+    
+    if(cc == NULL || cc->nodes == NULL)
+    {
+        return;
+    }
+
+    if (cc->err) {
+        redisCLusterClearAll(cc);
+    } else {
+        redisCLusterSendAll(cc);
+        
+        do {
+            status = redisClusterGetReply(cc, &reply);
+            if (status == REDIS_OK) {
+                freeReplyObject(reply);
+            } else {
+                redisCLusterClearAll(cc);
+                break;
+            }
+        } while(reply != NULL);
+    }
+    
+    if(cc->requests)
+    {
+        listRelease(cc->requests);
+        cc->requests = NULL;
+    }
+
+    if(cc->need_update_route)
+    {
+        status = cluster_update_route(cc);
+        if(status != REDIS_OK)
+        {
+            __redisClusterSetError(cc, REDIS_ERR_OTHER, 
+                "route update error, please recreate redisClusterContext!");
+            return;
+        }
+        cc->need_update_route = 0;
+    }
+}
+
+/*############redis cluster async############*/
+
+/* We want the error field to be accessible directly instead of requiring
+ * an indirection to the redisContext struct. */
+static void __redisClusterAsyncCopyError(redisClusterAsyncContext *acc) {
+    if (!acc)
+        return;
+
+    redisClusterContext *cc = acc->cc;
+    acc->err = cc->err;
+    memcpy(acc->errstr, cc->errstr, 128);
+}
+
+static void __redisClusterAsyncSetError(redisClusterAsyncContext *acc, 
+    int type, const char *str) {
+    
+    size_t len;
+
+    acc->err = type;
+    if (str != NULL) {
+        len = strlen(str);
+        len = len < (sizeof(acc->errstr)-1) ? len : (sizeof(acc->errstr)-1);
+        memcpy(acc->errstr,str,len);
+        acc->errstr[len] = '\0';
+    } else {
+        /* Only REDIS_ERR_IO may lack a description! */
+        assert(type == REDIS_ERR_IO);
+        __redis_strerror_r(errno, acc->errstr, sizeof(acc->errstr));
+    }
+}
+
+static redisClusterAsyncContext *redisClusterAsyncInitialize(redisClusterContext *cc) {
+    redisClusterAsyncContext *acc;
+
+    if(cc == NULL)
+    {
+        return NULL;
+    }
+
+    acc = hi_alloc(sizeof(redisClusterAsyncContext));
+    if (acc == NULL)
+        return NULL;
+
+    acc->cc = cc;
+
+    acc->err = 0;
+    acc->data = NULL;
+    acc->adapter = NULL;
+    acc->attach_fn = NULL;
+
+    acc->onConnect = NULL;
+    acc->onDisconnect = NULL;
+
+    return acc;
+}
+
+static cluster_async_data *cluster_async_data_get(void)
+{
+    cluster_async_data *cad;
+
+    cad = hi_alloc(sizeof(cluster_async_data));
+    if(cad == NULL)
+    {
+        return NULL;
+    }
+
+    cad->acc = NULL;
+    cad->command = NULL;
+    cad->callback = NULL;
+    cad->privdata = NULL;
+    cad->retry_count = 0;
+
+    return cad;
+}
+
+static void cluster_async_data_free(cluster_async_data *cad)
+{
+    if(cad == NULL)
+    {
+        return;
+    }
+
+    if(cad->command != NULL)
+    {
+        command_destroy(cad->command);
+    }
+    
+    hi_free(cad);
+    cad = NULL;
+}
+
+static void unlinkAsyncContextAndNode(redisAsyncContext* ac)
+{
+    cluster_node *node;
+
+    if (ac->data) {
+        node = (cluster_node *)(ac->data);
+        node->acon = NULL;
+    }
+}
+
+redisAsyncContext * actx_get_by_node(redisClusterAsyncContext *acc, 
+    cluster_node *node)
+{
+    redisAsyncContext *ac;
+    
+    if(node == NULL)
+    {
+        return NULL;
+    }
+
+    ac = node->acon;
+    if(ac != NULL)
+    {
+        if (ac->c.err == 0) {
+            return ac;
+        } else {
+            NOT_REACHED();
+        }
+    }
+
+    if(node->host == NULL || node->port <= 0)
+    {
+        __redisClusterAsyncSetError(acc, REDIS_ERR_OTHER, "node host or port is error");
+        return NULL;
+    }
+
+    ac = redisAsyncConnect(node->host, node->port);
+    if(ac == NULL)
+    {
+        __redisClusterAsyncSetError(acc, REDIS_ERR_OTHER, "node host or port is error");
+        return NULL;
+    }
+
+    if(acc->adapter)
+    {
+        acc->attach_fn(ac, acc->adapter);
+    }
+
+    if(acc->onConnect)
+    {
+        redisAsyncSetConnectCallback(ac, acc->onConnect);
+    }
+
+    if(acc->onDisconnect)
+    {
+        redisAsyncSetDisconnectCallback(ac, acc->onDisconnect);
+    }
+
+    ac->data = node;
+    ac->dataHandler = unlinkAsyncContextAndNode;
+    node->acon = ac;
+    
+    return ac;
+}
+
+static redisAsyncContext *actx_get_after_update_route_by_slot(
+    redisClusterAsyncContext *acc, int slot_num)
+{
+    int ret;
+    redisClusterContext *cc;
+    redisAsyncContext *ac;
+    cluster_node *node;
+
+    if(acc == NULL || slot_num < 0)
+    {
+        return NULL;
+    }
+
+    cc = acc->cc;
+    if(cc == NULL)
+    {
+        return NULL;
+    }
+    
+    ret = cluster_update_route(cc);
+    if(ret != REDIS_OK)
+    {
+        __redisClusterAsyncSetError(acc, REDIS_ERR_OTHER, 
+            "route update error, please recreate redisClusterContext!");
+        return NULL;
+    }
+
+    node = node_get_by_table(cc, (uint32_t)slot_num);
+    if(node == NULL)
+    {
+        __redisClusterAsyncSetError(acc, 
+            REDIS_ERR_OTHER, "node get by table error");
+        return NULL;
+    }
+
+    ac = actx_get_by_node(acc, node);
+    if(ac == NULL)
+    {
+        __redisClusterAsyncSetError(acc, 
+            REDIS_ERR_OTHER, "actx get by node error");
+        return NULL;
+    }
+    else if(ac->err)
+    {
+        __redisClusterAsyncSetError(acc, ac->err, ac->errstr);
+        return NULL;
+    }
+
+    return ac;
+}
+
+redisClusterAsyncContext *redisClusterAsyncConnect(const char *addrs, int flags) {
+
+    redisClusterContext *cc;
+    redisClusterAsyncContext *acc;
+
+    cc = redisClusterConnectNonBlock(addrs, flags);
+    if(cc == NULL)
+    {
+        return NULL;
+    }
+
+    acc = redisClusterAsyncInitialize(cc);
+    if (acc == NULL) {
+        redisClusterFree(cc);
+        return NULL;
+    }
+    
+    __redisClusterAsyncCopyError(acc);
+    
+    return acc;
+}
+
+
+int redisClusterAsyncSetConnectCallback(
+    redisClusterAsyncContext *acc, redisConnectCallback *fn) 
+{    
+    if (acc->onConnect == NULL) {
+        acc->onConnect = fn;
+        return REDIS_OK;
+    }
+    return REDIS_ERR;
+}
+
+int redisClusterAsyncSetDisconnectCallback(
+    redisClusterAsyncContext *acc, redisDisconnectCallback *fn)
+{
+    if (acc->onDisconnect == NULL) {
+        acc->onDisconnect = fn;
+        return REDIS_OK;
+    }
+    return REDIS_ERR;
+}
+
+static void redisClusterAsyncCallback(redisAsyncContext *ac, void *r, void *privdata) {
+    int ret;
+    redisReply *reply = r;
+    cluster_async_data *cad = privdata;
+    redisClusterAsyncContext *acc;
+    redisClusterContext *cc;
+    redisAsyncContext *ac_retry = NULL;
+    int error_type;
+    cluster_node *node;
+    struct cmd *command;
+    int64_t now, next;
+
+    if(cad == NULL)
+    {
+        goto error;
+    }
+
+    acc = cad->acc;
+    if(acc == NULL)
+    {
+        goto error;
+    }
+
+    cc = acc->cc;
+    if(cc == NULL)
+    {
+        goto error;
+    }
+
+    command = cad->command;
+    if(command == NULL)
+    {
+        goto error;
+    }
+    
+    if(reply == NULL)
+    {
+        //Note: 
+        //I can't decide witch is the best way to deal with connect 
+        //problem for hiredis cluster async api.
+        //But now the way is : when enough null reply for a node,
+        //we will update the route after the cluster node timeout.
+        //If you have a better idea, please contact with me. Thank you.
+        //My email: [email protected]
+        
+        node = (cluster_node *)(ac->data);
+        ASSERT(node != NULL);
+        
+        __redisClusterAsyncSetError(acc, 
+            ac->err, ac->errstr);
+        
+        if(cc->update_route_time != 0)
+        {
+            now = hi_usec_now();
+            if(now >= cc->update_route_time)
+            {
+                ret = cluster_update_route(cc);
+                if(ret != REDIS_OK)
+                {
+                    __redisClusterAsyncSetError(acc, REDIS_ERR_OTHER, 
+                        "route update error, please recreate redisClusterContext!");
+                }
+                
+                cc->update_route_time = 0LL;
+            }
+            
+            goto done;
+        }
+        
+        node->failure_count ++;
+        if(node->failure_count > cc->max_redirect_count)
+        {
+            char *cluster_timeout_str;
+            int cluster_timeout_str_len;
+            int cluster_timeout;
+
+            node->failure_count = 0;
+            if(cc->update_route_time != 0)
+            {
+                goto done;
+            }
+            
+            cluster_timeout_str = cluster_config_get(cc, 
+                "cluster-node-timeout", &cluster_timeout_str_len);
+            if(cluster_timeout_str == NULL)
+            {
+                __redisClusterAsyncSetError(acc, 
+                    cc->err, cc->errstr);
+                goto done;
+            }
+
+            cluster_timeout = hi_atoi(cluster_timeout_str, 
+                cluster_timeout_str_len);
+            free(cluster_timeout_str);
+            if(cluster_timeout <= 0)
+            {
+                __redisClusterAsyncSetError(acc, 
+                    REDIS_ERR_OTHER, 
+                    "cluster_timeout_str convert to integer error");
+                goto done;
+            }
+
+            now = hi_usec_now();
+            if (now < 0) {
+                __redisClusterAsyncSetError(acc, 
+                    REDIS_ERR_OTHER, 
+                    "get now usec time error");
+                goto done;
+            }
+
+            next = now + (cluster_timeout * 1000LL);
+
+            cc->update_route_time = next;
+            
+        }
+
+        goto done;
+    }
+
+    error_type = cluster_reply_error_type(reply);
+
+    if(error_type > CLUSTER_NOT_ERR && error_type < CLUSTER_ERR_SENTINEL)
+    {
+        cad->retry_count ++;
+        if(cad->retry_count > cc->max_redirect_count)
+        {
+            cad->retry_count = 0;
+            __redisClusterAsyncSetError(acc, 
+                REDIS_ERR_CLUSTER_TOO_MANY_REDIRECT, 
+                "too many cluster redirect");
+            goto done;
+        }
+        
+        switch(error_type)
+        {
+        case CLUSTER_ERR_MOVED:
+            ac_retry = actx_get_after_update_route_by_slot(acc, command->slot_num);
+            if(ac_retry == NULL)
+            {
+                goto done;
+            }
+            
+            break;
+        case CLUSTER_ERR_ASK:
+            node = node_get_by_ask_error_reply(cc, reply);
+            if(node == NULL)
+            {
+                __redisClusterAsyncSetError(acc, 
+                    cc->err, cc->errstr);
+                goto done;
+            }
+
+            ac_retry = actx_get_by_node(acc, node);
+            if(ac_retry == NULL)
+            {
+                __redisClusterAsyncSetError(acc, 
+                    REDIS_ERR_OTHER, "actx get by node error");
+                goto done;
+            }
+            else if(ac_retry->err)
+            {
+                __redisClusterAsyncSetError(acc, 
+                    ac_retry->err, ac_retry->errstr);
+                goto done;
+            }
+
+            ret = redisAsyncCommand(ac_retry,
+                NULL,NULL,REDIS_COMMAND_ASKING);
+            if(ret != REDIS_OK)
+            {
+                goto error;
+            }
+            
+            break;
+        case CLUSTER_ERR_TRYAGAIN:
+        case CLUSTER_ERR_CROSSSLOT:
+        case CLUSTER_ERR_CLUSTERDOWN:
+            ac_retry = ac;
+            
+            break;
+        default:
+
+            goto done;
+            break;
+        }
+
+        goto retry;
+    }
+
+done:
+
+    if(acc->err)
+    {
+        cad->callback(acc, NULL, cad->privdata);
+    }
+    else
+    {
+        cad->callback(acc, r, cad->privdata);
+    }
+
+    if(cc->err)
+    {
+        cc->err = 0;
+        memset(cc->errstr, '\0', strlen(cc->errstr));
+    }
+
+    if(acc->err)
+    {
+        acc->err = 0;
+        memset(acc->errstr, '\0', strlen(acc->errstr));
+    }
+    
+    if(cad != NULL)
+    {
+        cluster_async_data_free(cad);
+    }
+
+    return;
+
+retry:
+
+    ret = redisAsyncFormattedCommand(ac_retry,
+        redisClusterAsyncCallback,cad,command->cmd,command->clen);
+    if(ret != REDIS_OK)
+    {
+        goto error;
+    }
+    
+    return;
+
+error:
+
+    if(cad != NULL)
+    {
+        cluster_async_data_free(cad);
+    }
+}
+
+int redisClusterAsyncFormattedCommand(redisClusterAsyncContext *acc, 
+    redisClusterCallbackFn *fn, void *privdata, char *cmd, int len) {
+    
+    redisClusterContext *cc;
+    int status = REDIS_OK;
+    int slot_num;
+    cluster_node *node;
+    redisAsyncContext *ac;
+    struct cmd *command = NULL;
+    hilist *commands = NULL;
+    cluster_async_data *cad;
+
+    if(acc == NULL)
+    {
+        return REDIS_ERR;
+    }
+
+    cc = acc->cc;
+
+    if(cc->err)
+    {
+        cc->err = 0;
+        memset(cc->errstr, '\0', strlen(cc->errstr));
+    }
+
+    if(acc->err)
+    {
+        acc->err = 0;
+        memset(acc->errstr, '\0', strlen(acc->errstr));
+    }
+
+    command = command_get();
+    if(command == NULL)
+    {
+        __redisClusterAsyncSetError(acc,REDIS_ERR_OOM,"Out of memory");
+        goto error;
+    }
+    
+    command->cmd = malloc(len*sizeof(*command->cmd));
+    if(command->cmd == NULL)
+    {
+        __redisClusterAsyncSetError(acc,REDIS_ERR_OOM,"Out of memory");
+        goto error;
+    }
+    memcpy(command->cmd, cmd, len);
+    command->clen = len;
+
+    commands = listCreate();
+    if(commands == NULL)
+    {
+        __redisClusterAsyncSetError(acc,REDIS_ERR_OOM,"Out of memory");
+        goto error;
+    }
+
+    commands->free = listCommandFree;
+
+    slot_num = command_format_by_slot(cc, command, commands);
+
+    if(slot_num < 0)
+    {
+        __redisClusterAsyncSetError(acc,
+            cc->err, cc->errstr);
+        goto error;
+    }
+    else if(slot_num >= REDIS_CLUSTER_SLOTS)
+    {
+        __redisClusterAsyncSetError(acc,
+            REDIS_ERR_OTHER,"slot_num is out of range");
+        goto error;
+    }
+
+    //all keys not belong to one slot
+    if(listLength(commands) > 0)
+    {
+        ASSERT(listLength(commands) != 1);
+        
+        __redisClusterAsyncSetError(acc,REDIS_ERR_OTHER,
+            "Asynchronous API now not support multi-key command");
+        goto error;
+    }
+
+    node = node_get_by_table(cc, (uint32_t) slot_num);
+    if(node == NULL)
+    {
+        __redisClusterAsyncSetError(acc, 
+            REDIS_ERR_OTHER, "node get by table error");
+        goto error;
+    }
+    
+    ac = actx_get_by_node(acc, node);
+    if(ac == NULL)
+    {
+        __redisClusterAsyncSetError(acc, 
+            REDIS_ERR_OTHER, "actx get by node error");
+        goto error;
+    }
+    else if(ac->err)
+    {
+        __redisClusterAsyncSetError(acc, ac->err, ac->errstr);
+        goto error;
+    }
+
+    cad = cluster_async_data_get();
+    if(cad == NULL)
+    {
+        __redisClusterAsyncSetError(acc,REDIS_ERR_OOM,"Out of memory");
+        goto error;
+    }
+
+    cad->acc = acc;
+    cad->command = command;
+    cad->callback = fn;
+    cad->privdata = privdata;
+    
+    status = redisAsyncFormattedCommand(ac,
+        redisClusterAsyncCallback,cad,cmd,len);
+    if(status != REDIS_OK)
+    {
+        goto error;
+    }
+
+    if(commands != NULL)
+    {
+        listRelease(commands);
+    }
+
+    return REDIS_OK;
+
+error: 
+    
+    if(command != NULL)
+    {
+        command_destroy(command);
+    }
+
+    if(commands != NULL)
+    {
+        listRelease(commands);
+    }
+
+    return REDIS_ERR;
+}
+
+
+int redisClustervAsyncCommand(redisClusterAsyncContext *acc, 
+    redisClusterCallbackFn *fn, void *privdata, const char *format, va_list ap) {
+    int ret;
+    char *cmd;
+    int len;
+
+    if(acc == NULL)
+    {
+        return REDIS_ERR;
+    }
+
+    len = redisvFormatCommand(&cmd,format,ap);
+    if (len == -1) {
+        __redisClusterAsyncSetError(acc,REDIS_ERR_OOM,"Out of memory");
+        return REDIS_ERR;
+    } else if (len == -2) {
+        __redisClusterAsyncSetError(acc,REDIS_ERR_OTHER,"Invalid format string");
+        return REDIS_ERR;
+    }
+
+    ret = redisClusterAsyncFormattedCommand(acc, fn, privdata, cmd, len);
+
+    free(cmd);
+
+    return ret;
+}
+
+int redisClusterAsyncCommand(redisClusterAsyncContext *acc, 
+    redisClusterCallbackFn *fn, void *privdata, const char *format, ...) {
+    int ret;
+    va_list ap;
+
+    va_start(ap,format);
+    ret = redisClustervAsyncCommand(acc, fn, privdata, format, ap);
+    va_end(ap);
+
+    return ret;
+}
+
+int redisClusterAsyncCommandArgv(redisClusterAsyncContext *acc, 
+    redisClusterCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen) {
+    int ret;
+    char *cmd;
+    int len;
+    
+    len = redisFormatCommandArgv(&cmd,argc,argv,argvlen);
+    if (len == -1) {
+        __redisClusterAsyncSetError(acc,REDIS_ERR_OOM,"Out of memory");
+        return REDIS_ERR;
+    }
+
+    ret = redisClusterAsyncFormattedCommand(acc, fn, privdata, cmd, len);
+
+    free(cmd);
+
+    return ret;
+}
+
+void redisClusterAsyncDisconnect(redisClusterAsyncContext *acc) {
+
+    redisClusterContext *cc;
+    redisAsyncContext *ac;
+    dictIterator *di;
+    dictEntry *de;
+    dict *nodes;
+    struct cluster_node *node;
+
+    if(acc == NULL)
+    {
+        return;
+    }
+
+    cc = acc->cc;
+
+    nodes = cc->nodes;
+
+    if(nodes == NULL)
+    {
+        return;
+    }
+    
+    di = dictGetIterator(nodes);
+
+    while((de = dictNext(di)) != NULL) 
+    {
+        node = dictGetEntryVal(de);
+
+        ac = node->acon;
+
+        if(ac == NULL || ac->err)
+        {
+            continue;
+        }
+
+        redisAsyncDisconnect(ac);
+
+        node->acon = NULL;
+    }
+}
+
+void redisClusterAsyncFree(redisClusterAsyncContext *acc)
+{
+    redisClusterContext *cc;
+    
+    if(acc == NULL)
+    {
+        return;
+    }
+
+    cc = acc->cc;
+
+    redisClusterFree(cc);
+
+    hi_free(acc);
+}
+

+ 178 - 0
ext/hiredis-vip-0.3.0/hircluster.h

@@ -0,0 +1,178 @@
+
+#ifndef __HIRCLUSTER_H
+#define __HIRCLUSTER_H
+
+#include "hiredis.h"
+#include "async.h"
+
+#define HIREDIS_VIP_MAJOR 0
+#define HIREDIS_VIP_MINOR 3
+#define HIREDIS_VIP_PATCH 0
+
+#define REDIS_CLUSTER_SLOTS 16384
+
+#define REDIS_ROLE_NULL     0
+#define REDIS_ROLE_MASTER   1
+#define REDIS_ROLE_SLAVE    2
+
+
+#define HIRCLUSTER_FLAG_NULL                0x0
+/* The flag to decide whether add slave node in 
+  * redisClusterContext->nodes. This is set in the
+  * least significant bit of the flags field in 
+  * redisClusterContext. (1000000000000) */
+#define HIRCLUSTER_FLAG_ADD_SLAVE           0x1000
+/* The flag to decide whether add open slot  
+  * for master node. (10000000000000) */
+#define HIRCLUSTER_FLAG_ADD_OPENSLOT        0x2000
+/* The flag to decide whether get the route 
+  * table by 'cluster slots' command. Default   
+  * is 'cluster nodes' command.*/
+#define HIRCLUSTER_FLAG_ROUTE_USE_SLOTS     0x4000
+
+struct dict;
+struct hilist;
+
+typedef struct cluster_node
+{
+    sds name;
+    sds addr;
+    sds host;
+    int port;
+    uint8_t role;
+    uint8_t myself;   /* myself ? */
+    redisContext *con;
+    redisAsyncContext *acon;
+    struct hilist *slots;
+    struct hilist *slaves;
+    int failure_count;
+    void *data;     /* Not used by hiredis */
+    struct hiarray *migrating;  /* copen_slot[] */
+    struct hiarray *importing;  /* copen_slot[] */
+}cluster_node;
+
+typedef struct cluster_slot
+{
+    uint32_t start;
+    uint32_t end;
+    cluster_node *node; /* master that this slot region belong to */
+}cluster_slot;
+
+typedef struct copen_slot
+{
+    uint32_t slot_num;  /* slot number */
+    int migrate;        /* migrating or importing? */
+    sds remote_name;    /* name for the node that this slot migrating to/importing from */
+    cluster_node *node; /* master that this slot belong to */
+}copen_slot;
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Context for a connection to Redis cluster */
+typedef struct redisClusterContext {
+    int err; /* Error flags, 0 when there is no error */
+    char errstr[128]; /* String representation of error when applicable */
+    sds ip;
+    int port;
+
+    int flags;
+
+    enum redisConnectionType connection_type;
+    struct timeval *timeout;
+    
+    struct hiarray *slots;
+
+    struct dict *nodes;
+    cluster_node *table[REDIS_CLUSTER_SLOTS];
+
+    uint64_t route_version;
+
+    int max_redirect_count;
+    int retry_count;
+
+    struct hilist *requests;
+
+    int need_update_route;
+    int64_t update_route_time;
+} redisClusterContext;
+
+redisClusterContext *redisClusterConnect(const char *addrs, int flags);
+redisClusterContext *redisClusterConnectWithTimeout(const char *addrs, 
+    const struct timeval tv, int flags);
+redisClusterContext *redisClusterConnectNonBlock(const char *addrs, int flags);
+
+void redisClusterFree(redisClusterContext *cc);
+
+void redisClusterSetMaxRedirect(redisClusterContext *cc, int max_redirect_count);
+
+void *redisClusterFormattedCommand(redisClusterContext *cc, char *cmd, int len);
+void *redisClustervCommand(redisClusterContext *cc, const char *format, va_list ap);
+void *redisClusterCommand(redisClusterContext *cc, const char *format, ...);
+void *redisClusterCommandArgv(redisClusterContext *cc, int argc, const char **argv, const size_t *argvlen);
+
+redisContext *ctx_get_by_node(struct cluster_node *node, const struct timeval *timeout, int flags);
+
+int redisClusterAppendFormattedCommand(redisClusterContext *cc, char *cmd, int len);
+int redisClustervAppendCommand(redisClusterContext *cc, const char *format, va_list ap);
+int redisClusterAppendCommand(redisClusterContext *cc, const char *format, ...);
+int redisClusterAppendCommandArgv(redisClusterContext *cc, int argc, const char **argv, const size_t *argvlen);
+int redisClusterGetReply(redisClusterContext *cc, void **reply);
+void redisClusterReset(redisClusterContext *cc);
+
+int cluster_update_route(redisClusterContext *cc);
+int test_cluster_update_route(redisClusterContext *cc);
+struct dict *parse_cluster_nodes(redisClusterContext *cc, char *str, int str_len, int flags);
+struct dict *parse_cluster_slots(redisClusterContext *cc, redisReply *reply, int flags);
+
+
+/*############redis cluster async############*/
+
+struct redisClusterAsyncContext;
+
+typedef int (adapterAttachFn)(redisAsyncContext*, void*);
+
+typedef void (redisClusterCallbackFn)(struct redisClusterAsyncContext*, void*, void*);
+
+/* Context for an async connection to Redis */
+typedef struct redisClusterAsyncContext {
+    
+    redisClusterContext *cc;
+
+    /* Setup error flags so they can be used directly. */
+    int err;
+    char errstr[128]; /* String representation of error when applicable */
+
+    /* Not used by hiredis */
+    void *data;
+
+    void *adapter;
+    adapterAttachFn *attach_fn;
+
+    /* Called when either the connection is terminated due to an error or per
+     * user request. The status is set accordingly (REDIS_OK, REDIS_ERR). */
+    redisDisconnectCallback *onDisconnect;
+
+    /* Called when the first write event was received. */
+    redisConnectCallback *onConnect;
+
+} redisClusterAsyncContext;
+
+redisClusterAsyncContext *redisClusterAsyncConnect(const char *addrs, int flags);
+int redisClusterAsyncSetConnectCallback(redisClusterAsyncContext *acc, redisConnectCallback *fn);
+int redisClusterAsyncSetDisconnectCallback(redisClusterAsyncContext *acc, redisDisconnectCallback *fn);
+int redisClusterAsyncFormattedCommand(redisClusterAsyncContext *acc, redisClusterCallbackFn *fn, void *privdata, char *cmd, int len);
+int redisClustervAsyncCommand(redisClusterAsyncContext *acc, redisClusterCallbackFn *fn, void *privdata, const char *format, va_list ap);
+int redisClusterAsyncCommand(redisClusterAsyncContext *acc, redisClusterCallbackFn *fn, void *privdata, const char *format, ...);
+int redisClusterAsyncCommandArgv(redisClusterAsyncContext *acc, redisClusterCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen);
+void redisClusterAsyncDisconnect(redisClusterAsyncContext *acc);
+void redisClusterAsyncFree(redisClusterAsyncContext *acc);
+
+redisAsyncContext *actx_get_by_node(redisClusterAsyncContext *acc, cluster_node *node);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif

+ 1021 - 0
ext/hiredis-vip-0.3.0/hiredis.c

@@ -0,0 +1,1021 @@
+/*
+ * Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com>
+ * Copyright (c) 2010-2014, Pieter Noordhuis <pcnoordhuis at gmail dot com>
+ * Copyright (c) 2015, Matt Stancliff <matt at genges dot com>,
+ *                     Jan-Erik Rediger <janerik at fnordig dot com>
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Redis nor the names of its contributors may be used
+ *     to endorse or promote products derived from this software without
+ *     specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "fmacros.h"
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <assert.h>
+#include <errno.h>
+#include <ctype.h>
+
+#include "hiredis.h"
+#include "net.h"
+#include "sds.h"
+
+static redisReply *createReplyObject(int type);
+static void *createStringObject(const redisReadTask *task, char *str, size_t len);
+static void *createArrayObject(const redisReadTask *task, int elements);
+static void *createIntegerObject(const redisReadTask *task, long long value);
+static void *createNilObject(const redisReadTask *task);
+
+/* Default set of functions to build the reply. Keep in mind that such a
+ * function returning NULL is interpreted as OOM. */
+static redisReplyObjectFunctions defaultFunctions = {
+    createStringObject,
+    createArrayObject,
+    createIntegerObject,
+    createNilObject,
+    freeReplyObject
+};
+
+/* Create a reply object */
+static redisReply *createReplyObject(int type) {
+    redisReply *r = calloc(1,sizeof(*r));
+
+    if (r == NULL)
+        return NULL;
+
+    r->type = type;
+    return r;
+}
+
+/* Free a reply object */
+void freeReplyObject(void *reply) {
+    redisReply *r = reply;
+    size_t j;
+
+    if (r == NULL)
+        return;
+
+    switch(r->type) {
+    case REDIS_REPLY_INTEGER:
+        break; /* Nothing to free */
+    case REDIS_REPLY_ARRAY:
+        if (r->element != NULL) {
+            for (j = 0; j < r->elements; j++)
+                if (r->element[j] != NULL)
+                    freeReplyObject(r->element[j]);
+            free(r->element);
+        }
+        break;
+    case REDIS_REPLY_ERROR:
+    case REDIS_REPLY_STATUS:
+    case REDIS_REPLY_STRING:
+        if (r->str != NULL)
+            free(r->str);
+        break;
+    }
+    free(r);
+}
+
+static void *createStringObject(const redisReadTask *task, char *str, size_t len) {
+    redisReply *r, *parent;
+    char *buf;
+
+    r = createReplyObject(task->type);
+    if (r == NULL)
+        return NULL;
+
+    buf = malloc(len+1);
+    if (buf == NULL) {
+        freeReplyObject(r);
+        return NULL;
+    }
+
+    assert(task->type == REDIS_REPLY_ERROR  ||
+           task->type == REDIS_REPLY_STATUS ||
+           task->type == REDIS_REPLY_STRING);
+
+    /* Copy string value */
+    memcpy(buf,str,len);
+    buf[len] = '\0';
+    r->str = buf;
+    r->len = len;
+
+    if (task->parent) {
+        parent = task->parent->obj;
+        assert(parent->type == REDIS_REPLY_ARRAY);
+        parent->element[task->idx] = r;
+    }
+    return r;
+}
+
+static void *createArrayObject(const redisReadTask *task, int elements) {
+    redisReply *r, *parent;
+
+    r = createReplyObject(REDIS_REPLY_ARRAY);
+    if (r == NULL)
+        return NULL;
+
+    if (elements > 0) {
+        r->element = calloc(elements,sizeof(redisReply*));
+        if (r->element == NULL) {
+            freeReplyObject(r);
+            return NULL;
+        }
+    }
+
+    r->elements = elements;
+
+    if (task->parent) {
+        parent = task->parent->obj;
+        assert(parent->type == REDIS_REPLY_ARRAY);
+        parent->element[task->idx] = r;
+    }
+    return r;
+}
+
+static void *createIntegerObject(const redisReadTask *task, long long value) {
+    redisReply *r, *parent;
+
+    r = createReplyObject(REDIS_REPLY_INTEGER);
+    if (r == NULL)
+        return NULL;
+
+    r->integer = value;
+
+    if (task->parent) {
+        parent = task->parent->obj;
+        assert(parent->type == REDIS_REPLY_ARRAY);
+        parent->element[task->idx] = r;
+    }
+    return r;
+}
+
+static void *createNilObject(const redisReadTask *task) {
+    redisReply *r, *parent;
+
+    r = createReplyObject(REDIS_REPLY_NIL);
+    if (r == NULL)
+        return NULL;
+
+    if (task->parent) {
+        parent = task->parent->obj;
+        assert(parent->type == REDIS_REPLY_ARRAY);
+        parent->element[task->idx] = r;
+    }
+    return r;
+}
+
+/* Return the number of digits of 'v' when converted to string in radix 10.
+ * Implementation borrowed from link in redis/src/util.c:string2ll(). */
+static uint32_t countDigits(uint64_t v) {
+  uint32_t result = 1;
+  for (;;) {
+    if (v < 10) return result;
+    if (v < 100) return result + 1;
+    if (v < 1000) return result + 2;
+    if (v < 10000) return result + 3;
+    v /= 10000U;
+    result += 4;
+  }
+}
+
+/* Helper that calculates the bulk length given a certain string length. */
+static size_t bulklen(size_t len) {
+    return 1+countDigits(len)+2+len+2;
+}
+
+int redisvFormatCommand(char **target, const char *format, va_list ap) {
+    const char *c = format;
+    char *cmd = NULL; /* final command */
+    int pos; /* position in final command */
+    sds curarg, newarg; /* current argument */
+    int touched = 0; /* was the current argument touched? */
+    char **curargv = NULL, **newargv = NULL;
+    int argc = 0;
+    int totlen = 0;
+    int error_type = 0; /* 0 = no error; -1 = memory error; -2 = format error */
+    int j;
+
+    /* Abort if there is not target to set */
+    if (target == NULL)
+        return -1;
+
+    /* Build the command string accordingly to protocol */
+    curarg = sdsempty();
+    if (curarg == NULL)
+        return -1;
+
+    while(*c != '\0') {
+        if (*c != '%' || c[1] == '\0') {
+            if (*c == ' ') {
+                if (touched) {
+                    newargv = realloc(curargv,sizeof(char*)*(argc+1));
+                    if (newargv == NULL) goto memory_err;
+                    curargv = newargv;
+                    curargv[argc++] = curarg;
+                    totlen += bulklen(sdslen(curarg));
+
+                    /* curarg is put in argv so it can be overwritten. */
+                    curarg = sdsempty();
+                    if (curarg == NULL) goto memory_err;
+                    touched = 0;
+                }
+            } else {
+                newarg = sdscatlen(curarg,c,1);
+                if (newarg == NULL) goto memory_err;
+                curarg = newarg;
+                touched = 1;
+            }
+        } else {
+            char *arg;
+            size_t size;
+
+            /* Set newarg so it can be checked even if it is not touched. */
+            newarg = curarg;
+
+            switch(c[1]) {
+            case 's':
+                arg = va_arg(ap,char*);
+                size = strlen(arg);
+                if (size > 0)
+                    newarg = sdscatlen(curarg,arg,size);
+                break;
+            case 'b':
+                arg = va_arg(ap,char*);
+                size = va_arg(ap,size_t);
+                if (size > 0)
+                    newarg = sdscatlen(curarg,arg,size);
+                break;
+            case '%':
+                newarg = sdscat(curarg,"%");
+                break;
+            default:
+                /* Try to detect printf format */
+                {
+                    static const char intfmts[] = "diouxX";
+                    static const char flags[] = "#0-+ ";
+                    char _format[16];
+                    const char *_p = c+1;
+                    size_t _l = 0;
+                    va_list _cpy;
+
+                    /* Flags */
+                    while (*_p != '\0' && strchr(flags,*_p) != NULL) _p++;
+
+                    /* Field width */
+                    while (*_p != '\0' && isdigit(*_p)) _p++;
+
+                    /* Precision */
+                    if (*_p == '.') {
+                        _p++;
+                        while (*_p != '\0' && isdigit(*_p)) _p++;
+                    }
+
+                    /* Copy va_list before consuming with va_arg */
+                    va_copy(_cpy,ap);
+
+                    /* Integer conversion (without modifiers) */
+                    if (strchr(intfmts,*_p) != NULL) {
+                        va_arg(ap,int);
+                        goto fmt_valid;
+                    }
+
+                    /* Double conversion (without modifiers) */
+                    if (strchr("eEfFgGaA",*_p) != NULL) {
+                        va_arg(ap,double);
+                        goto fmt_valid;
+                    }
+
+                    /* Size: char */
+                    if (_p[0] == 'h' && _p[1] == 'h') {
+                        _p += 2;
+                        if (*_p != '\0' && strchr(intfmts,*_p) != NULL) {
+                            va_arg(ap,int); /* char gets promoted to int */
+                            goto fmt_valid;
+                        }
+                        goto fmt_invalid;
+                    }
+
+                    /* Size: short */
+                    if (_p[0] == 'h') {
+                        _p += 1;
+                        if (*_p != '\0' && strchr(intfmts,*_p) != NULL) {
+                            va_arg(ap,int); /* short gets promoted to int */
+                            goto fmt_valid;
+                        }
+                        goto fmt_invalid;
+                    }
+
+                    /* Size: long long */
+                    if (_p[0] == 'l' && _p[1] == 'l') {
+                        _p += 2;
+                        if (*_p != '\0' && strchr(intfmts,*_p) != NULL) {
+                            va_arg(ap,long long);
+                            goto fmt_valid;
+                        }
+                        goto fmt_invalid;
+                    }
+
+                    /* Size: long */
+                    if (_p[0] == 'l') {
+                        _p += 1;
+                        if (*_p != '\0' && strchr(intfmts,*_p) != NULL) {
+                            va_arg(ap,long);
+                            goto fmt_valid;
+                        }
+                        goto fmt_invalid;
+                    }
+
+                fmt_invalid:
+                    va_end(_cpy);
+                    goto format_err;
+
+                fmt_valid:
+                    _l = (_p+1)-c;
+                    if (_l < sizeof(_format)-2) {
+                        memcpy(_format,c,_l);
+                        _format[_l] = '\0';
+                        newarg = sdscatvprintf(curarg,_format,_cpy);
+
+                        /* Update current position (note: outer blocks
+                         * increment c twice so compensate here) */
+                        c = _p-1;
+                    }
+
+                    va_end(_cpy);
+                    break;
+                }
+            }
+
+            if (newarg == NULL) goto memory_err;
+            curarg = newarg;
+
+            touched = 1;
+            c++;
+        }
+        c++;
+    }
+
+    /* Add the last argument if needed */
+    if (touched) {
+        newargv = realloc(curargv,sizeof(char*)*(argc+1));
+        if (newargv == NULL) goto memory_err;
+        curargv = newargv;
+        curargv[argc++] = curarg;
+        totlen += bulklen(sdslen(curarg));
+    } else {
+        sdsfree(curarg);
+    }
+
+    /* Clear curarg because it was put in curargv or was free'd. */
+    curarg = NULL;
+
+    /* Add bytes needed to hold multi bulk count */
+    totlen += 1+countDigits(argc)+2;
+
+    /* Build the command at protocol level */
+    cmd = malloc(totlen+1);
+    if (cmd == NULL) goto memory_err;
+
+    pos = sprintf(cmd,"*%d\r\n",argc);
+    for (j = 0; j < argc; j++) {
+        pos += sprintf(cmd+pos,"$%zu\r\n",sdslen(curargv[j]));
+        memcpy(cmd+pos,curargv[j],sdslen(curargv[j]));
+        pos += sdslen(curargv[j]);
+        sdsfree(curargv[j]);
+        cmd[pos++] = '\r';
+        cmd[pos++] = '\n';
+    }
+    assert(pos == totlen);
+    cmd[pos] = '\0';
+
+    free(curargv);
+    *target = cmd;
+    return totlen;
+
+format_err:
+    error_type = -2;
+    goto cleanup;
+
+memory_err:
+    error_type = -1;
+    goto cleanup;
+
+cleanup:
+    if (curargv) {
+        while(argc--)
+            sdsfree(curargv[argc]);
+        free(curargv);
+    }
+
+    sdsfree(curarg);
+
+    /* No need to check cmd since it is the last statement that can fail,
+     * but do it anyway to be as defensive as possible. */
+    if (cmd != NULL)
+        free(cmd);
+
+    return error_type;
+}
+
+/* Format a command according to the Redis protocol. This function
+ * takes a format similar to printf:
+ *
+ * %s represents a C null terminated string you want to interpolate
+ * %b represents a binary safe string
+ *
+ * When using %b you need to provide both the pointer to the string
+ * and the length in bytes as a size_t. Examples:
+ *
+ * len = redisFormatCommand(target, "GET %s", mykey);
+ * len = redisFormatCommand(target, "SET %s %b", mykey, myval, myvallen);
+ */
+int redisFormatCommand(char **target, const char *format, ...) {
+    va_list ap;
+    int len;
+    va_start(ap,format);
+    len = redisvFormatCommand(target,format,ap);
+    va_end(ap);
+
+    /* The API says "-1" means bad result, but we now also return "-2" in some
+     * cases.  Force the return value to always be -1. */
+    if (len < 0)
+        len = -1;
+
+    return len;
+}
+
+/* Format a command according to the Redis protocol using an 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(sds *target, int argc, const char **argv,
+                              const size_t *argvlen)
+{
+    sds cmd;
+    unsigned long long totlen;
+    int j;
+    size_t len;
+
+    /* Abort on a NULL target */
+    if (target == NULL)
+        return -1;
+
+    /* Calculate our total size */
+    totlen = 1+countDigits(argc)+2;
+    for (j = 0; j < argc; j++) {
+        len = argvlen ? argvlen[j] : strlen(argv[j]);
+        totlen += bulklen(len);
+    }
+
+    /* Use an SDS string for command construction */
+    cmd = sdsempty();
+    if (cmd == NULL)
+        return -1;
+
+    /* We already know how much storage we need */
+    cmd = sdsMakeRoomFor(cmd, totlen);
+    if (cmd == NULL)
+        return -1;
+
+    /* Construct command */
+    cmd = sdscatfmt(cmd, "*%i\r\n", argc);
+    for (j=0; j < argc; j++) {
+        len = argvlen ? argvlen[j] : strlen(argv[j]);
+        cmd = sdscatfmt(cmd, "$%T\r\n", len);
+        cmd = sdscatlen(cmd, argv[j], len);
+        cmd = sdscatlen(cmd, "\r\n", sizeof("\r\n")-1);
+    }
+
+    assert(sdslen(cmd)==totlen);
+
+    *target = cmd;
+    return totlen;
+}
+
+void redisFreeSdsCommand(sds cmd) {
+    sdsfree(cmd);
+}
+
+/* Format a command according to the Redis protocol. This function takes the
+ * number of arguments, an array with arguments and an array with their
+ * lengths. If the latter is set to NULL, strlen will be used to compute the
+ * argument lengths.
+ */
+int 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;
+
+    /* Abort on a NULL target */
+    if (target == NULL)
+        return -1;
+
+    /* Calculate number of bytes needed for the command */
+    totlen = 1+countDigits(argc)+2;
+    for (j = 0; j < argc; j++) {
+        len = argvlen ? argvlen[j] : strlen(argv[j]);
+        totlen += bulklen(len);
+    }
+
+    /* Build the command at protocol level */
+    cmd = malloc(totlen+1);
+    if (cmd == NULL)
+        return -1;
+
+    pos = sprintf(cmd,"*%d\r\n",argc);
+    for (j = 0; j < argc; j++) {
+        len = argvlen ? argvlen[j] : strlen(argv[j]);
+        pos += sprintf(cmd+pos,"$%zu\r\n",len);
+        memcpy(cmd+pos,argv[j],len);
+        pos += len;
+        cmd[pos++] = '\r';
+        cmd[pos++] = '\n';
+    }
+    assert(pos == totlen);
+    cmd[pos] = '\0';
+
+    *target = cmd;
+    return totlen;
+}
+
+void redisFreeCommand(char *cmd) {
+    free(cmd);
+}
+
+void __redisSetError(redisContext *c, int type, const char *str) {
+    size_t len;
+
+    c->err = type;
+    if (str != NULL) {
+        len = strlen(str);
+        len = len < (sizeof(c->errstr)-1) ? len : (sizeof(c->errstr)-1);
+        memcpy(c->errstr,str,len);
+        c->errstr[len] = '\0';
+    } else {
+        /* Only REDIS_ERR_IO may lack a description! */
+        assert(type == REDIS_ERR_IO);
+        __redis_strerror_r(errno, c->errstr, sizeof(c->errstr));
+    }
+}
+
+redisReader *redisReaderCreate(void) {
+    return redisReaderCreateWithFunctions(&defaultFunctions);
+}
+
+static redisContext *redisContextInit(void) {
+    redisContext *c;
+
+    c = calloc(1,sizeof(redisContext));
+    if (c == NULL)
+        return NULL;
+
+    c->err = 0;
+    c->errstr[0] = '\0';
+    c->obuf = sdsempty();
+    c->reader = redisReaderCreate();
+    c->tcp.host = NULL;
+    c->tcp.source_addr = NULL;
+    c->unix_sock.path = NULL;
+    c->timeout = NULL;
+
+    if (c->obuf == NULL || c->reader == NULL) {
+        redisFree(c);
+        return NULL;
+    }
+
+    return c;
+}
+
+void redisFree(redisContext *c) {
+    if (c == NULL)
+        return;
+    if (c->fd > 0)
+        close(c->fd);
+    if (c->obuf != NULL)
+        sdsfree(c->obuf);
+    if (c->reader != NULL)
+        redisReaderFree(c->reader);
+    if (c->tcp.host)
+        free(c->tcp.host);
+    if (c->tcp.source_addr)
+        free(c->tcp.source_addr);
+    if (c->unix_sock.path)
+        free(c->unix_sock.path);
+    if (c->timeout)
+        free(c->timeout);
+    free(c);
+}
+
+int redisFreeKeepFd(redisContext *c) {
+    int fd = c->fd;
+    c->fd = -1;
+    redisFree(c);
+    return fd;
+}
+
+int redisReconnect(redisContext *c) {
+    c->err = 0;
+    memset(c->errstr, '\0', strlen(c->errstr));
+
+    if (c->fd > 0) {
+        close(c->fd);
+    }
+
+    sdsfree(c->obuf);
+    redisReaderFree(c->reader);
+
+    c->obuf = sdsempty();
+    c->reader = redisReaderCreate();
+
+    if (c->connection_type == REDIS_CONN_TCP) {
+        return redisContextConnectBindTcp(c, c->tcp.host, c->tcp.port,
+                c->timeout, c->tcp.source_addr);
+    } else if (c->connection_type == REDIS_CONN_UNIX) {
+        return redisContextConnectUnix(c, c->unix_sock.path, c->timeout);
+    } else {
+        /* Something bad happened here and shouldn't have. There isn't
+           enough information in the context to reconnect. */
+        __redisSetError(c,REDIS_ERR_OTHER,"Not enough information to reconnect");
+    }
+
+    return REDIS_ERR;
+}
+
+/* Connect to a Redis instance. On error the field error in the returned
+ * context will be set to the return value of the error function.
+ * When no set of reply functions is given, the default set will be used. */
+redisContext *redisConnect(const char *ip, int port) {
+    redisContext *c;
+
+    c = redisContextInit();
+    if (c == NULL)
+        return NULL;
+
+    c->flags |= REDIS_BLOCK;
+    redisContextConnectTcp(c,ip,port,NULL);
+    return c;
+}
+
+redisContext *redisConnectWithTimeout(const char *ip, int port, const struct timeval tv) {
+    redisContext *c;
+
+    c = redisContextInit();
+    if (c == NULL)
+        return NULL;
+
+    c->flags |= REDIS_BLOCK;
+    redisContextConnectTcp(c,ip,port,&tv);
+    return c;
+}
+
+redisContext *redisConnectNonBlock(const char *ip, int port) {
+    redisContext *c;
+
+    c = redisContextInit();
+    if (c == NULL)
+        return NULL;
+
+    c->flags &= ~REDIS_BLOCK;
+    redisContextConnectTcp(c,ip,port,NULL);
+    return c;
+}
+
+redisContext *redisConnectBindNonBlock(const char *ip, int port,
+                                       const char *source_addr) {
+    redisContext *c = redisContextInit();
+    c->flags &= ~REDIS_BLOCK;
+    redisContextConnectBindTcp(c,ip,port,NULL,source_addr);
+    return c;
+}
+
+redisContext *redisConnectBindNonBlockWithReuse(const char *ip, int port,
+                                                const char *source_addr) {
+    redisContext *c = redisContextInit();
+    c->flags &= ~REDIS_BLOCK;
+    c->flags |= REDIS_REUSEADDR;
+    redisContextConnectBindTcp(c,ip,port,NULL,source_addr);
+    return c;
+}
+
+redisContext *redisConnectUnix(const char *path) {
+    redisContext *c;
+
+    c = redisContextInit();
+    if (c == NULL)
+        return NULL;
+
+    c->flags |= REDIS_BLOCK;
+    redisContextConnectUnix(c,path,NULL);
+    return c;
+}
+
+redisContext *redisConnectUnixWithTimeout(const char *path, const struct timeval tv) {
+    redisContext *c;
+
+    c = redisContextInit();
+    if (c == NULL)
+        return NULL;
+
+    c->flags |= REDIS_BLOCK;
+    redisContextConnectUnix(c,path,&tv);
+    return c;
+}
+
+redisContext *redisConnectUnixNonBlock(const char *path) {
+    redisContext *c;
+
+    c = redisContextInit();
+    if (c == NULL)
+        return NULL;
+
+    c->flags &= ~REDIS_BLOCK;
+    redisContextConnectUnix(c,path,NULL);
+    return c;
+}
+
+redisContext *redisConnectFd(int fd) {
+    redisContext *c;
+
+    c = redisContextInit();
+    if (c == NULL)
+        return NULL;
+
+    c->fd = fd;
+    c->flags |= REDIS_BLOCK | REDIS_CONNECTED;
+    return c;
+}
+
+/* Set read/write timeout on a blocking socket. */
+int redisSetTimeout(redisContext *c, const struct timeval tv) {
+    if (c->flags & REDIS_BLOCK)
+        return redisContextSetTimeout(c,tv);
+    return REDIS_ERR;
+}
+
+/* Enable connection KeepAlive. */
+int redisEnableKeepAlive(redisContext *c) {
+    if (redisKeepAlive(c, REDIS_KEEPALIVE_INTERVAL) != REDIS_OK)
+        return REDIS_ERR;
+    return REDIS_OK;
+}
+
+/* Use this function to handle a read event on the descriptor. It will try
+ * and read some bytes from the socket and feed them to the reply parser.
+ *
+ * After this function is called, you may use redisContextReadReply to
+ * see if there is a reply available. */
+int redisBufferRead(redisContext *c) {
+    char buf[1024*16];
+    int nread;
+
+    /* Return early when the context has seen an error. */
+    if (c->err)
+        return REDIS_ERR;
+
+    nread = read(c->fd,buf,sizeof(buf));
+    if (nread == -1) {
+        if ((errno == EAGAIN && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) {
+            /* Try again later */
+        } else {
+            __redisSetError(c,REDIS_ERR_IO,NULL);
+            return REDIS_ERR;
+        }
+    } else if (nread == 0) {
+        __redisSetError(c,REDIS_ERR_EOF,"Server closed the connection");
+        return REDIS_ERR;
+    } else {
+        if (redisReaderFeed(c->reader,buf,nread) != REDIS_OK) {
+            __redisSetError(c,c->reader->err,c->reader->errstr);
+            return REDIS_ERR;
+        }
+    }
+    return REDIS_OK;
+}
+
+/* Write the output buffer to the socket.
+ *
+ * Returns REDIS_OK when the buffer is empty, or (a part of) the buffer was
+ * succesfully written to the socket. When the buffer is empty after the
+ * write operation, "done" is set to 1 (if given).
+ *
+ * Returns REDIS_ERR if an error occured trying to write and sets
+ * c->errstr to hold the appropriate error string.
+ */
+int redisBufferWrite(redisContext *c, int *done) {
+    int nwritten;
+
+    /* Return early when the context has seen an error. */
+    if (c->err)
+        return REDIS_ERR;
+
+    if (sdslen(c->obuf) > 0) {
+        nwritten = write(c->fd,c->obuf,sdslen(c->obuf));
+        if (nwritten == -1) {
+            if ((errno == EAGAIN && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) {
+                /* Try again later */
+            } else {
+                __redisSetError(c,REDIS_ERR_IO,NULL);
+                return REDIS_ERR;
+            }
+        } else if (nwritten > 0) {
+            if (nwritten == (signed)sdslen(c->obuf)) {
+                sdsfree(c->obuf);
+                c->obuf = sdsempty();
+            } else {
+                sdsrange(c->obuf,nwritten,-1);
+            }
+        }
+    }
+    if (done != NULL) *done = (sdslen(c->obuf) == 0);
+    return REDIS_OK;
+}
+
+/* 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;
+}
+
+int redisGetReply(redisContext *c, void **reply) {
+    int wdone = 0;
+    void *aux = NULL;
+
+    /* Try to read pending replies */
+    if (redisGetReplyFromReader(c,&aux) == REDIS_ERR)
+        return REDIS_ERR;
+
+    /* For the blocking context, flush output buffer and read reply */
+    if (aux == NULL && c->flags & REDIS_BLOCK) {
+        /* Write until done */
+        do {
+            if (redisBufferWrite(c,&wdone) == REDIS_ERR)
+                return REDIS_ERR;
+        } while (!wdone);
+
+        /* Read until there is a reply */
+        do {
+            if (redisBufferRead(c) == REDIS_ERR)
+                return REDIS_ERR;
+            if (redisGetReplyFromReader(c,&aux) == REDIS_ERR)
+                return REDIS_ERR;
+        } while (aux == NULL);
+    }
+
+    /* Set reply object */
+    if (reply != NULL) *reply = aux;
+    return REDIS_OK;
+}
+
+
+/* Helper function for the redisAppendCommand* family of functions.
+ *
+ * Write a formatted command to the output buffer. When this family
+ * is used, you need to call redisGetReply yourself to retrieve
+ * the reply (or replies in pub/sub).
+ */
+int __redisAppendCommand(redisContext *c, const char *cmd, size_t len) {
+    sds newbuf;
+
+    newbuf = sdscatlen(c->obuf,cmd,len);
+    if (newbuf == NULL) {
+        __redisSetError(c,REDIS_ERR_OOM,"Out of memory");
+        return REDIS_ERR;
+    }
+
+    c->obuf = newbuf;
+    return REDIS_OK;
+}
+
+int redisAppendFormattedCommand(redisContext *c, const char *cmd, size_t len) {
+
+    if (__redisAppendCommand(c, cmd, len) != REDIS_OK) {
+        return REDIS_ERR;
+    }
+
+    return REDIS_OK;
+}
+
+int redisvAppendCommand(redisContext *c, const char *format, va_list ap) {
+    char *cmd;
+    int len;
+
+    len = redisvFormatCommand(&cmd,format,ap);
+    if (len == -1) {
+        __redisSetError(c,REDIS_ERR_OOM,"Out of memory");
+        return REDIS_ERR;
+    } else if (len == -2) {
+        __redisSetError(c,REDIS_ERR_OTHER,"Invalid format string");
+        return REDIS_ERR;
+    }
+
+    if (__redisAppendCommand(c,cmd,len) != REDIS_OK) {
+        free(cmd);
+        return REDIS_ERR;
+    }
+
+    free(cmd);
+    return REDIS_OK;
+}
+
+int redisAppendCommand(redisContext *c, const char *format, ...) {
+    va_list ap;
+    int ret;
+
+    va_start(ap,format);
+    ret = redisvAppendCommand(c,format,ap);
+    va_end(ap);
+    return ret;
+}
+
+int redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen) {
+    sds cmd;
+    int len;
+
+    len = redisFormatSdsCommandArgv(&cmd,argc,argv,argvlen);
+    if (len == -1) {
+        __redisSetError(c,REDIS_ERR_OOM,"Out of memory");
+        return REDIS_ERR;
+    }
+
+    if (__redisAppendCommand(c,cmd,len) != REDIS_OK) {
+        sdsfree(cmd);
+        return REDIS_ERR;
+    }
+
+    sdsfree(cmd);
+    return REDIS_OK;
+}
+
+/* Helper function for the redisCommand* family of functions.
+ *
+ * Write a formatted command to the output buffer. If the given context is
+ * blocking, immediately read the reply into the "reply" pointer. When the
+ * context is non-blocking, the "reply" pointer will not be used and the
+ * command is simply appended to the write buffer.
+ *
+ * Returns the reply when a reply was succesfully retrieved. Returns NULL
+ * otherwise. When NULL is returned in a blocking context, the error field
+ * in the context will be set.
+ */
+static void *__redisBlockForReply(redisContext *c) {
+    void *reply;
+
+    if (c->flags & REDIS_BLOCK) {
+        if (redisGetReply(c,&reply) != REDIS_OK)
+            return NULL;
+        return reply;
+    }
+    return NULL;
+}
+
+void *redisvCommand(redisContext *c, const char *format, va_list ap) {
+    if (redisvAppendCommand(c,format,ap) != REDIS_OK)
+        return NULL;
+    return __redisBlockForReply(c);
+}
+
+void *redisCommand(redisContext *c, const char *format, ...) {
+    va_list ap;
+    void *reply = NULL;
+    va_start(ap,format);
+    reply = redisvCommand(c,format,ap);
+    va_end(ap);
+    return reply;
+}
+
+void *redisCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen) {
+    if (redisAppendCommandArgv(c,argc,argv,argvlen) != REDIS_OK)
+        return NULL;
+    return __redisBlockForReply(c);
+}

+ 221 - 0
ext/hiredis-vip-0.3.0/hiredis.h

@@ -0,0 +1,221 @@
+/*
+ * Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com>
+ * Copyright (c) 2010-2014, Pieter Noordhuis <pcnoordhuis at gmail dot com>
+ * Copyright (c) 2015, Matt Stancliff <matt at genges dot com>,
+ *                     Jan-Erik Rediger <janerik at fnordig dot com>
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Redis nor the names of its contributors may be used
+ *     to endorse or promote products derived from this software without
+ *     specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __HIREDIS_H
+#define __HIREDIS_H
+#include "read.h"
+#include <stdarg.h> /* for va_list */
+#include <sys/time.h> /* for struct timeval */
+#include <stdint.h> /* uintXX_t, etc */
+#include "sds.h" /* for sds */
+
+#define HIREDIS_MAJOR 0
+#define HIREDIS_MINOR 13
+#define HIREDIS_PATCH 1
+
+/* Connection type can be blocking or non-blocking and is set in the
+ * least significant bit of the flags field in redisContext. */
+#define REDIS_BLOCK 0x1
+
+/* Connection may be disconnected before being free'd. The second bit
+ * in the flags field is set when the context is connected. */
+#define REDIS_CONNECTED 0x2
+
+/* The async API might try to disconnect cleanly and flush the output
+ * buffer and read all subsequent replies before disconnecting.
+ * This flag means no new commands can come in and the connection
+ * should be terminated once all replies have been read. */
+#define REDIS_DISCONNECTING 0x4
+
+/* Flag specific to the async API which means that the context should be clean
+ * up as soon as possible. */
+#define REDIS_FREEING 0x8
+
+/* Flag that is set when an async callback is executed. */
+#define REDIS_IN_CALLBACK 0x10
+
+/* Flag that is set when the async context has one or more subscriptions. */
+#define REDIS_SUBSCRIBED 0x20
+
+/* Flag that is set when monitor mode is active */
+#define REDIS_MONITORING 0x40
+
+/* Flag that is set when we should set SO_REUSEADDR before calling bind() */
+#define REDIS_REUSEADDR 0x80
+
+#define REDIS_KEEPALIVE_INTERVAL 15 /* seconds */
+
+/* number of times we retry to connect in the case of EADDRNOTAVAIL and
+ * SO_REUSEADDR is being used. */
+#define REDIS_CONNECT_RETRIES  10
+
+/* strerror_r has two completely different prototypes and behaviors
+ * depending on system issues, so we need to operate on the error buffer
+ * differently depending on which strerror_r we're using. */
+#ifndef _GNU_SOURCE
+/* "regular" POSIX strerror_r that does the right thing. */
+#define __redis_strerror_r(errno, buf, len)                                    \
+    do {                                                                       \
+        strerror_r((errno), (buf), (len));                                     \
+    } while (0)
+#else
+/* "bad" GNU strerror_r we need to clean up after. */
+#define __redis_strerror_r(errno, buf, len)                                    \
+    do {                                                                       \
+        char *err_str = strerror_r((errno), (buf), (len));                     \
+        /* If return value _isn't_ the start of the buffer we passed in,       \
+         * then GNU strerror_r returned an internal static buffer and we       \
+         * need to copy the result into our private buffer. */                 \
+        if (err_str != (buf)) {                                                \
+            buf[(len)] = '\0';                                                 \
+            strncat((buf), err_str, ((len) - 1));                              \
+        }                                                                      \
+    } while (0)
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* This is the reply object returned by redisCommand() */
+typedef struct redisReply {
+    int type; /* REDIS_REPLY_* */
+    long long integer; /* The integer when type is REDIS_REPLY_INTEGER */
+    int len; /* Length of string */
+    char *str; /* Used for both REDIS_REPLY_ERROR and REDIS_REPLY_STRING */
+    size_t elements; /* number of elements, for REDIS_REPLY_ARRAY */
+    struct redisReply **element; /* elements vector for REDIS_REPLY_ARRAY */
+} redisReply;
+
+redisReader *redisReaderCreate(void);
+
+/* Function to free the reply objects hiredis returns by default. */
+void freeReplyObject(void *reply);
+
+/* Functions to format a command according to the protocol. */
+int redisvFormatCommand(char **target, const char *format, va_list ap);
+int redisFormatCommand(char **target, const char *format, ...);
+int redisFormatCommandArgv(char **target, int argc, const char **argv, const size_t *argvlen);
+int redisFormatSdsCommandArgv(sds *target, int argc, const char ** argv, const size_t *argvlen);
+void redisFreeCommand(char *cmd);
+void redisFreeSdsCommand(sds cmd);
+
+enum redisConnectionType {
+    REDIS_CONN_TCP,
+    REDIS_CONN_UNIX,
+};
+
+/* Context for a connection to Redis */
+typedef struct redisContext {
+    int err; /* Error flags, 0 when there is no error */
+    char errstr[128]; /* String representation of error when applicable */
+    int fd;
+    int flags;
+    char *obuf; /* Write buffer */
+    redisReader *reader; /* Protocol reader */
+
+    enum redisConnectionType connection_type;
+    struct timeval *timeout;
+
+    struct {
+        char *host;
+        char *source_addr;
+        int port;
+    } tcp;
+
+    struct {
+        char *path;
+    } unix_sock;
+} redisContext;
+
+redisContext *redisConnect(const char *ip, int port);
+redisContext *redisConnectWithTimeout(const char *ip, int port, const struct timeval tv);
+redisContext *redisConnectNonBlock(const char *ip, int port);
+redisContext *redisConnectBindNonBlock(const char *ip, int port,
+                                       const char *source_addr);
+redisContext *redisConnectBindNonBlockWithReuse(const char *ip, int port,
+                                                const char *source_addr);
+redisContext *redisConnectUnix(const char *path);
+redisContext *redisConnectUnixWithTimeout(const char *path, const struct timeval tv);
+redisContext *redisConnectUnixNonBlock(const char *path);
+redisContext *redisConnectFd(int fd);
+
+/**
+ * Reconnect the given context using the saved information.
+ *
+ * This re-uses the exact same connect options as in the initial connection.
+ * host, ip (or path), timeout and bind address are reused,
+ * flags are used unmodified from the existing context.
+ *
+ * Returns REDIS_OK on successfull connect or REDIS_ERR otherwise.
+ */
+int redisReconnect(redisContext *c);
+
+int redisSetTimeout(redisContext *c, const struct timeval tv);
+int redisEnableKeepAlive(redisContext *c);
+void redisFree(redisContext *c);
+int redisFreeKeepFd(redisContext *c);
+int redisBufferRead(redisContext *c);
+int redisBufferWrite(redisContext *c, int *done);
+
+/* In a blocking context, this function first checks if there are unconsumed
+ * replies to return and returns one if so. Otherwise, it flushes the output
+ * buffer to the socket and reads until it has a reply. In a non-blocking
+ * context, it will return unconsumed replies until there are no more. */
+int redisGetReply(redisContext *c, void **reply);
+int redisGetReplyFromReader(redisContext *c, void **reply);
+
+/* Write a formatted command to the output buffer. Use these functions in blocking mode
+ * to get a pipeline of commands. */
+int redisAppendFormattedCommand(redisContext *c, const char *cmd, size_t len);
+
+/* Write a command to the output buffer. Use these functions in blocking mode
+ * to get a pipeline of commands. */
+int redisvAppendCommand(redisContext *c, const char *format, va_list ap);
+int redisAppendCommand(redisContext *c, const char *format, ...);
+int redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen);
+
+/* Issue a command to Redis. In a blocking context, it is identical to calling
+ * redisAppendCommand, followed by redisGetReply. The function will return
+ * NULL if there was an error in performing the request, otherwise it will
+ * return the reply. In a non-blocking context, it is identical to calling
+ * only redisAppendCommand and will always return NULL. */
+void *redisvCommand(redisContext *c, const char *format, va_list ap);
+void *redisCommand(redisContext *c, const char *format, ...);
+void *redisCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif

+ 554 - 0
ext/hiredis-vip-0.3.0/hiutil.c

@@ -0,0 +1,554 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#include <sys/time.h>
+#include <sys/types.h>
+
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+
+
+#include "hiutil.h"
+
+#ifdef HI_HAVE_BACKTRACE
+# include <execinfo.h>
+#endif
+
+int
+hi_set_blocking(int sd)
+{
+    int flags;
+
+    flags = fcntl(sd, F_GETFL, 0);
+    if (flags < 0) {
+        return flags;
+    }
+
+    return fcntl(sd, F_SETFL, flags & ~O_NONBLOCK);
+}
+
+int
+hi_set_nonblocking(int sd)
+{
+    int flags;
+
+    flags = fcntl(sd, F_GETFL, 0);
+    if (flags < 0) {
+        return flags;
+    }
+
+    return fcntl(sd, F_SETFL, flags | O_NONBLOCK);
+}
+
+int
+hi_set_reuseaddr(int sd)
+{
+    int reuse;
+    socklen_t len;
+
+    reuse = 1;
+    len = sizeof(reuse);
+
+    return setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, &reuse, len);
+}
+
+/*
+ * Disable Nagle algorithm on TCP socket.
+ *
+ * This option helps to minimize transmit latency by disabling coalescing
+ * of data to fill up a TCP segment inside the kernel. Sockets with this
+ * option must use readv() or writev() to do data transfer in bulk and
+ * hence avoid the overhead of small packets.
+ */
+int
+hi_set_tcpnodelay(int sd)
+{
+    int nodelay;
+    socklen_t len;
+
+    nodelay = 1;
+    len = sizeof(nodelay);
+
+    return setsockopt(sd, IPPROTO_TCP, TCP_NODELAY, &nodelay, len);
+}
+
+int
+hi_set_linger(int sd, int timeout)
+{
+    struct linger linger;
+    socklen_t len;
+
+    linger.l_onoff = 1;
+    linger.l_linger = timeout;
+
+    len = sizeof(linger);
+
+    return setsockopt(sd, SOL_SOCKET, SO_LINGER, &linger, len);
+}
+
+int
+hi_set_sndbuf(int sd, int size)
+{
+    socklen_t len;
+
+    len = sizeof(size);
+
+    return setsockopt(sd, SOL_SOCKET, SO_SNDBUF, &size, len);
+}
+
+int
+hi_set_rcvbuf(int sd, int size)
+{
+    socklen_t len;
+
+    len = sizeof(size);
+
+    return setsockopt(sd, SOL_SOCKET, SO_RCVBUF, &size, len);
+}
+
+int
+hi_get_soerror(int sd)
+{
+    int status, err;
+    socklen_t len;
+
+    err = 0;
+    len = sizeof(err);
+
+    status = getsockopt(sd, SOL_SOCKET, SO_ERROR, &err, &len);
+    if (status == 0) {
+        errno = err;
+    }
+
+    return status;
+}
+
+int
+hi_get_sndbuf(int sd)
+{
+    int status, size;
+    socklen_t len;
+
+    size = 0;
+    len = sizeof(size);
+
+    status = getsockopt(sd, SOL_SOCKET, SO_SNDBUF, &size, &len);
+    if (status < 0) {
+        return status;
+    }
+
+    return size;
+}
+
+int
+hi_get_rcvbuf(int sd)
+{
+    int status, size;
+    socklen_t len;
+
+    size = 0;
+    len = sizeof(size);
+
+    status = getsockopt(sd, SOL_SOCKET, SO_RCVBUF, &size, &len);
+    if (status < 0) {
+        return status;
+    }
+
+    return size;
+}
+
+int
+_hi_atoi(uint8_t *line, size_t n)
+{
+    int value;
+
+    if (n == 0) {
+        return -1;
+    }
+
+    for (value = 0; n--; line++) {
+        if (*line < '0' || *line > '9') {
+            return -1;
+        }
+
+        value = value * 10 + (*line - '0');
+    }
+
+    if (value < 0) {
+        return -1;
+    }
+
+    return value;
+}
+
+void 
+_hi_itoa(uint8_t *s, int num)
+{
+    uint8_t c;
+    uint8_t sign = 0;
+
+    if(s == NULL)
+    {
+        return;
+    }
+
+    uint32_t len, i;
+    len = 0;
+
+    if(num < 0)
+    {
+        sign = 1;
+        num = abs(num);
+    }
+    else if(num == 0)
+    {
+        s[len++] = '0';
+        return;
+    }
+
+    while(num % 10 || num /10)
+    {
+        c = num %10 + '0';
+        num = num /10;
+        s[len+1] = s[len];
+        s[len] = c;
+        len ++;
+    }
+
+    if(sign == 1)
+    {
+        s[len++] = '-';
+    }
+
+    s[len] = '\0';
+    
+    for(i = 0; i < len/2; i ++)
+    {
+        c = s[i];
+        s[i] = s[len - i -1];
+        s[len - i -1] = c;
+    }
+
+}
+
+
+int
+hi_valid_port(int n)
+{
+    if (n < 1 || n > UINT16_MAX) {
+        return 0;
+    }
+
+    return 1;
+}
+
+int _uint_len(uint32_t num)
+{
+    int n = 0;
+
+    if(num == 0)
+    {
+        return 1;
+    }
+
+    while(num != 0)
+    {
+        n ++;
+        num /= 10;
+    }
+
+    return n;
+}
+
+void *
+_hi_alloc(size_t size, const char *name, int line)
+{
+    void *p;
+
+    ASSERT(size != 0);
+
+    p = malloc(size);
+
+    if(name == NULL && line == 1)
+    {
+
+    }
+
+    return p;
+}
+
+void *
+_hi_zalloc(size_t size, const char *name, int line)
+{
+    void *p;
+
+    p = _hi_alloc(size, name, line);
+    if (p != NULL) {
+        memset(p, 0, size);
+    }
+
+    return p;
+}
+
+void *
+_hi_calloc(size_t nmemb, size_t size, const char *name, int line)
+{
+    return _hi_zalloc(nmemb * size, name, line);
+}
+
+void *
+_hi_realloc(void *ptr, size_t size, const char *name, int line)
+{
+    void *p;
+
+    ASSERT(size != 0);
+
+    p = realloc(ptr, size);
+
+    if(name == NULL && line == 1)
+    {
+
+    }
+    
+    return p;
+}
+
+void
+_hi_free(void *ptr, const char *name, int line)
+{
+    ASSERT(ptr != NULL);
+
+    if(name == NULL && line == 1)
+    {
+
+    }
+
+    free(ptr);
+}
+
+void
+hi_stacktrace(int skip_count)
+{
+    if(skip_count > 0)
+    {
+
+    }
+
+#ifdef HI_HAVE_BACKTRACE
+    void *stack[64];
+    char **symbols;
+    int size, i, j;
+
+    size = backtrace(stack, 64);
+    symbols = backtrace_symbols(stack, size);
+    if (symbols == NULL) {
+        return;
+    }
+
+    skip_count++; /* skip the current frame also */
+
+    for (i = skip_count, j = 0; i < size; i++, j++) {
+        printf("[%d] %s\n", j, symbols[i]);
+    }
+
+    free(symbols);
+#endif
+}
+
+void
+hi_stacktrace_fd(int fd)
+{
+    if(fd > 0)
+    {
+        
+    }
+#ifdef HI_HAVE_BACKTRACE
+    void *stack[64];
+    int size;
+
+    size = backtrace(stack, 64);
+    backtrace_symbols_fd(stack, size, fd);
+#endif
+}
+
+void
+hi_assert(const char *cond, const char *file, int line, int panic)
+{
+    
+    printf("File: %s Line: %d: %s\n", file, line, cond);
+    
+    if (panic) {
+        hi_stacktrace(1);
+        abort();
+    }
+    abort();
+}
+
+int
+_vscnprintf(char *buf, size_t size, const char *fmt, va_list args)
+{
+    int n;
+
+    n = vsnprintf(buf, size, fmt, args);
+
+    /*
+     * The return value is the number of characters which would be written
+     * into buf not including the trailing '\0'. If size is == 0 the
+     * function returns 0.
+     *
+     * On error, the function also returns 0. This is to allow idiom such
+     * as len += _vscnprintf(...)
+     *
+     * See: http://lwn.net/Articles/69419/
+     */
+    if (n <= 0) {
+        return 0;
+    }
+
+    if (n < (int) size) {
+        return n;
+    }
+
+    return (int)(size - 1);
+}
+
+int
+_scnprintf(char *buf, size_t size, const char *fmt, ...)
+{
+    va_list args;
+    int n;
+
+    va_start(args, fmt);
+    n = _vscnprintf(buf, size, fmt, args);
+    va_end(args);
+
+    return n;
+}
+
+/*
+ * Send n bytes on a blocking descriptor
+ */
+ssize_t
+_hi_sendn(int sd, const void *vptr, size_t n)
+{
+    size_t nleft;
+    ssize_t nsend;
+    const char *ptr;
+
+    ptr = vptr;
+    nleft = n;
+    while (nleft > 0) {
+        nsend = send(sd, ptr, nleft, 0);
+        if (nsend < 0) {
+            if (errno == EINTR) {
+                continue;
+            }
+            return nsend;
+        }
+        if (nsend == 0) {
+            return -1;
+        }
+
+        nleft -= (size_t)nsend;
+        ptr += nsend;
+    }
+
+    return (ssize_t)n;
+}
+
+/*
+ * Recv n bytes from a blocking descriptor
+ */
+ssize_t
+_hi_recvn(int sd, void *vptr, size_t n)
+{
+    size_t nleft;
+    ssize_t nrecv;
+    char *ptr;
+
+    ptr = vptr;
+    nleft = n;
+    while (nleft > 0) {
+        nrecv = recv(sd, ptr, nleft, 0);
+        if (nrecv < 0) {
+            if (errno == EINTR) {
+                continue;
+            }
+            return nrecv;
+        }
+        if (nrecv == 0) {
+            break;
+        }
+
+        nleft -= (size_t)nrecv;
+        ptr += nrecv;
+    }
+
+    return (ssize_t)(n - nleft);
+}
+
+/*
+ * Return the current time in microseconds since Epoch
+ */
+int64_t
+hi_usec_now(void)
+{
+    struct timeval now;
+    int64_t usec;
+    int status;
+
+    status = gettimeofday(&now, NULL);
+    if (status < 0) {
+        return -1;
+    }
+
+    usec = (int64_t)now.tv_sec * 1000000LL + (int64_t)now.tv_usec;
+
+    return usec;
+}
+
+/*
+ * Return the current time in milliseconds since Epoch
+ */
+int64_t
+hi_msec_now(void)
+{
+    return hi_usec_now() / 1000LL;
+}
+
+void print_string_with_length(char *s, size_t len)
+{
+    char *token;
+    for(token = s; token <= s + len; token ++)
+    {
+        printf("%c", *token);
+    }
+    printf("\n");
+}
+
+void print_string_with_length_fix_CRLF(char *s, size_t len)
+{
+    char *token;
+    for(token = s; token < s + len; token ++)
+    {
+        if(*token == CR)
+        {
+            printf("\\r");
+        }
+        else if(*token == LF)
+        {
+            printf("\\n");
+        }
+        else
+        {
+            printf("%c", *token);
+        }
+    }
+    printf("\n");
+}
+

+ 265 - 0
ext/hiredis-vip-0.3.0/hiutil.h

@@ -0,0 +1,265 @@
+#ifndef __HIUTIL_H_
+#define __HIUTIL_H_
+
+#include <stdarg.h>
+#include <stdint.h>
+#include <sys/types.h>
+
+#define HI_OK        0
+#define HI_ERROR    -1
+#define HI_EAGAIN   -2
+#define HI_ENOMEM   -3
+
+typedef int rstatus_t; /* return type */
+
+#define LF                  (uint8_t) 10
+#define CR                  (uint8_t) 13
+#define CRLF                "\x0d\x0a"
+#define CRLF_LEN            (sizeof("\x0d\x0a") - 1)
+
+#define NELEMS(a)           ((sizeof(a)) / sizeof((a)[0]))
+
+#define MIN(a, b)           ((a) < (b) ? (a) : (b))
+#define MAX(a, b)           ((a) > (b) ? (a) : (b))
+
+#define SQUARE(d)           ((d) * (d))
+#define VAR(s, s2, n)       (((n) < 2) ? 0.0 : ((s2) - SQUARE(s)/(n)) / ((n) - 1))
+#define STDDEV(s, s2, n)    (((n) < 2) ? 0.0 : sqrt(VAR((s), (s2), (n))))
+
+#define HI_INET4_ADDRSTRLEN (sizeof("255.255.255.255") - 1)
+#define HI_INET6_ADDRSTRLEN \
+    (sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255") - 1)
+#define HI_INET_ADDRSTRLEN  MAX(HI_INET4_ADDRSTRLEN, HI_INET6_ADDRSTRLEN)
+#define HI_UNIX_ADDRSTRLEN  \
+    (sizeof(struct sockaddr_un) - offsetof(struct sockaddr_un, sun_path))
+
+#define HI_MAXHOSTNAMELEN   256
+
+/*
+ * Length of 1 byte, 2 bytes, 4 bytes, 8 bytes and largest integral
+ * type (uintmax_t) in ascii, including the null terminator '\0'
+ *
+ * From stdint.h, we have:
+ * # define UINT8_MAX   (255)
+ * # define UINT16_MAX  (65535)
+ * # define UINT32_MAX  (4294967295U)
+ * # define UINT64_MAX  (__UINT64_C(18446744073709551615))
+ */
+#define HI_UINT8_MAXLEN     (3 + 1)
+#define HI_UINT16_MAXLEN    (5 + 1)
+#define HI_UINT32_MAXLEN    (10 + 1)
+#define HI_UINT64_MAXLEN    (20 + 1)
+#define HI_UINTMAX_MAXLEN   HI_UINT64_MAXLEN
+
+/*
+ * Make data 'd' or pointer 'p', n-byte aligned, where n is a power of 2
+ * of 2.
+ */
+#define HI_ALIGNMENT        sizeof(unsigned long) /* platform word */
+#define HI_ALIGN(d, n)      (((d) + (n - 1)) & ~(n - 1))
+#define HI_ALIGN_PTR(p, n)  \
+    (void *) (((uintptr_t) (p) + ((uintptr_t) n - 1)) & ~((uintptr_t) n - 1))
+
+
+
+#define str3icmp(m, c0, c1, c2)                                                             \
+    ((m[0] == c0 || m[0] == (c0 ^ 0x20)) &&                                                 \
+     (m[1] == c1 || m[1] == (c1 ^ 0x20)) &&                                                 \
+     (m[2] == c2 || m[2] == (c2 ^ 0x20)))
+
+#define str4icmp(m, c0, c1, c2, c3)                                                         \
+    (str3icmp(m, c0, c1, c2) && (m[3] == c3 || m[3] == (c3 ^ 0x20)))
+
+#define str5icmp(m, c0, c1, c2, c3, c4)                                                     \
+    (str4icmp(m, c0, c1, c2, c3) && (m[4] == c4 || m[4] == (c4 ^ 0x20)))
+
+#define str6icmp(m, c0, c1, c2, c3, c4, c5)                                                 \
+    (str5icmp(m, c0, c1, c2, c3, c4) && (m[5] == c5 || m[5] == (c5 ^ 0x20)))
+
+#define str7icmp(m, c0, c1, c2, c3, c4, c5, c6)                                             \
+    (str6icmp(m, c0, c1, c2, c3, c4, c5) &&                                                 \
+     (m[6] == c6 || m[6] == (c6 ^ 0x20)))
+
+#define str8icmp(m, c0, c1, c2, c3, c4, c5, c6, c7)                                         \
+    (str7icmp(m, c0, c1, c2, c3, c4, c5, c6) &&                                             \
+     (m[7] == c7 || m[7] == (c7 ^ 0x20)))
+
+#define str9icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8)                                     \
+    (str8icmp(m, c0, c1, c2, c3, c4, c5, c6, c7) &&                                         \
+     (m[8] == c8 || m[8] == (c8 ^ 0x20)))
+
+#define str10icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9)                                \
+    (str9icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8) &&                                     \
+     (m[9] == c9 || m[9] == (c9 ^ 0x20)))
+
+#define str11icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10)                           \
+    (str10icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9) &&                                \
+     (m[10] == c10 || m[10] == (c10 ^ 0x20)))
+
+#define str12icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11)                      \
+    (str11icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10) &&                           \
+     (m[11] == c11 || m[11] == (c11 ^ 0x20)))
+
+#define str13icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12)                 \
+    (str12icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11) &&                      \
+     (m[12] == c12 || m[12] == (c12 ^ 0x20)))
+
+#define str14icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12, c13)            \
+    (str13icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12) &&                 \
+     (m[13] == c13 || m[13] == (c13 ^ 0x20)))
+
+#define str15icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12, c13, c14)       \
+    (str14icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12, c13) &&            \
+     (m[14] == c14 || m[14] == (c14 ^ 0x20)))
+
+#define str16icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12, c13, c14, c15)  \
+    (str15icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12, c13, c14) &&       \
+     (m[15] == c15 || m[15] == (c15 ^ 0x20)))
+
+
+
+/*
+ * Wrapper to workaround well known, safe, implicit type conversion when
+ * invoking system calls.
+ */
+#define hi_gethostname(_name, _len) \
+    gethostname((char *)_name, (size_t)_len)
+
+#define hi_atoi(_line, _n)          \
+    _hi_atoi((uint8_t *)_line, (size_t)_n)
+#define hi_itoa(_line, _n)          \
+        _hi_itoa((uint8_t *)_line, (int)_n)
+
+#define uint_len(_n)        \
+    _uint_len((uint32_t)_n)
+
+
+int hi_set_blocking(int sd);
+int hi_set_nonblocking(int sd);
+int hi_set_reuseaddr(int sd);
+int hi_set_tcpnodelay(int sd);
+int hi_set_linger(int sd, int timeout);
+int hi_set_sndbuf(int sd, int size);
+int hi_set_rcvbuf(int sd, int size);
+int hi_get_soerror(int sd);
+int hi_get_sndbuf(int sd);
+int hi_get_rcvbuf(int sd);
+
+int _hi_atoi(uint8_t *line, size_t n);
+void _hi_itoa(uint8_t *s, int num);
+
+int hi_valid_port(int n);
+
+int _uint_len(uint32_t num);
+
+
+/*
+ * Memory allocation and free wrappers.
+ *
+ * These wrappers enables us to loosely detect double free, dangling
+ * pointer access and zero-byte alloc.
+ */
+#define hi_alloc(_s)                    \
+    _hi_alloc((size_t)(_s), __FILE__, __LINE__)
+
+#define hi_zalloc(_s)                   \
+    _hi_zalloc((size_t)(_s), __FILE__, __LINE__)
+
+#define hi_calloc(_n, _s)               \
+    _hi_calloc((size_t)(_n), (size_t)(_s), __FILE__, __LINE__)
+
+#define hi_realloc(_p, _s)              \
+    _hi_realloc(_p, (size_t)(_s), __FILE__, __LINE__)
+
+#define hi_free(_p) do {                \
+    _hi_free(_p, __FILE__, __LINE__);   \
+    (_p) = NULL;                        \
+} while (0)
+
+void *_hi_alloc(size_t size, const char *name, int line);
+void *_hi_zalloc(size_t size, const char *name, int line);
+void *_hi_calloc(size_t nmemb, size_t size, const char *name, int line);
+void *_hi_realloc(void *ptr, size_t size, const char *name, int line);
+void _hi_free(void *ptr, const char *name, int line);
+
+
+#define hi_strndup(_s, _n)              \
+    strndup((char *)(_s), (size_t)(_n));
+
+/*
+ * Wrappers to send or receive n byte message on a blocking
+ * socket descriptor.
+ */
+#define hi_sendn(_s, _b, _n)    \
+    _hi_sendn(_s, _b, (size_t)(_n))
+
+#define hi_recvn(_s, _b, _n)    \
+    _hi_recvn(_s, _b, (size_t)(_n))
+
+/*
+ * Wrappers to read or write data to/from (multiple) buffers
+ * to a file or socket descriptor.
+ */
+#define hi_read(_d, _b, _n)     \
+    read(_d, _b, (size_t)(_n))
+
+#define hi_readv(_d, _b, _n)    \
+    readv(_d, _b, (int)(_n))
+
+#define hi_write(_d, _b, _n)    \
+    write(_d, _b, (size_t)(_n))
+
+#define hi_writev(_d, _b, _n)   \
+    writev(_d, _b, (int)(_n))
+
+ssize_t _hi_sendn(int sd, const void *vptr, size_t n);
+ssize_t _hi_recvn(int sd, void *vptr, size_t n);
+
+/*
+ * Wrappers for defining custom assert based on whether macro
+ * HI_ASSERT_PANIC or HI_ASSERT_LOG was defined at the moment
+ * ASSERT was called.
+ */
+#ifdef HI_ASSERT_PANIC
+
+#define ASSERT(_x) do {                         \
+    if (!(_x)) {                                \
+        hi_assert(#_x, __FILE__, __LINE__, 1);  \
+    }                                           \
+} while (0)
+
+#define NOT_REACHED() ASSERT(0)
+
+#elif HI_ASSERT_LOG
+
+#define ASSERT(_x) do {                         \
+    if (!(_x)) {                                \
+        hi_assert(#_x, __FILE__, __LINE__, 0);  \
+    }                                           \
+} while (0)
+
+#define NOT_REACHED() ASSERT(0)
+
+#else
+
+#define ASSERT(_x)
+
+#define NOT_REACHED()
+
+#endif
+
+void hi_assert(const char *cond, const char *file, int line, int panic);
+void hi_stacktrace(int skip_count);
+void hi_stacktrace_fd(int fd);
+
+int _scnprintf(char *buf, size_t size, const char *fmt, ...);
+int _vscnprintf(char *buf, size_t size, const char *fmt, va_list args);
+int64_t hi_usec_now(void);
+int64_t hi_msec_now(void);
+
+void print_string_with_length(char *s, size_t len);
+void print_string_with_length_fix_CRLF(char *s, size_t len);
+
+uint16_t crc16(const char *buf, int len);
+
+#endif

+ 458 - 0
ext/hiredis-vip-0.3.0/net.c

@@ -0,0 +1,458 @@
+/* Extracted from anet.c to work properly with Hiredis error reporting.
+ *
+ * Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com>
+ * Copyright (c) 2010-2014, Pieter Noordhuis <pcnoordhuis at gmail dot com>
+ * Copyright (c) 2015, Matt Stancliff <matt at genges dot com>,
+ *                     Jan-Erik Rediger <janerik at fnordig dot com>
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Redis nor the names of its contributors may be used
+ *     to endorse or promote products derived from this software without
+ *     specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "fmacros.h"
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/select.h>
+#include <sys/un.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <arpa/inet.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+#include <netdb.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <poll.h>
+#include <limits.h>
+#include <stdlib.h>
+
+#include "net.h"
+#include "sds.h"
+
+/* Defined in hiredis.c */
+void __redisSetError(redisContext *c, int type, const char *str);
+
+static void redisContextCloseFd(redisContext *c) {
+    if (c && c->fd >= 0) {
+        close(c->fd);
+        c->fd = -1;
+    }
+}
+
+static void __redisSetErrorFromErrno(redisContext *c, int type, const char *prefix) {
+    char buf[128] = { 0 };
+    size_t len = 0;
+
+    if (prefix != NULL)
+        len = snprintf(buf,sizeof(buf),"%s: ",prefix);
+    __redis_strerror_r(errno, (char *)(buf + len), sizeof(buf) - len);
+    __redisSetError(c,type,buf);
+}
+
+static int redisSetReuseAddr(redisContext *c) {
+    int on = 1;
+    if (setsockopt(c->fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) {
+        __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
+        redisContextCloseFd(c);
+        return REDIS_ERR;
+    }
+    return REDIS_OK;
+}
+
+static int redisCreateSocket(redisContext *c, int type) {
+    int s;
+    if ((s = socket(type, SOCK_STREAM, 0)) == -1) {
+        __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
+        return REDIS_ERR;
+    }
+    c->fd = s;
+    if (type == AF_INET) {
+        if (redisSetReuseAddr(c) == REDIS_ERR) {
+            return REDIS_ERR;
+        }
+    }
+    return REDIS_OK;
+}
+
+static int redisSetBlocking(redisContext *c, int blocking) {
+    int flags;
+
+    /* Set the socket nonblocking.
+     * Note that fcntl(2) for F_GETFL and F_SETFL can't be
+     * interrupted by a signal. */
+    if ((flags = fcntl(c->fd, F_GETFL)) == -1) {
+        __redisSetErrorFromErrno(c,REDIS_ERR_IO,"fcntl(F_GETFL)");
+        redisContextCloseFd(c);
+        return REDIS_ERR;
+    }
+
+    if (blocking)
+        flags &= ~O_NONBLOCK;
+    else
+        flags |= O_NONBLOCK;
+
+    if (fcntl(c->fd, F_SETFL, flags) == -1) {
+        __redisSetErrorFromErrno(c,REDIS_ERR_IO,"fcntl(F_SETFL)");
+        redisContextCloseFd(c);
+        return REDIS_ERR;
+    }
+    return REDIS_OK;
+}
+
+int redisKeepAlive(redisContext *c, int interval) {
+    int val = 1;
+    int fd = c->fd;
+
+    if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &val, sizeof(val)) == -1){
+        __redisSetError(c,REDIS_ERR_OTHER,strerror(errno));
+        return REDIS_ERR;
+    }
+
+    val = interval;
+
+#ifdef _OSX
+    if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPALIVE, &val, sizeof(val)) < 0) {
+        __redisSetError(c,REDIS_ERR_OTHER,strerror(errno));
+        return REDIS_ERR;
+    }
+#else
+#if defined(__GLIBC__) && !defined(__FreeBSD_kernel__)
+    val = interval;
+    if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE, &val, sizeof(val)) < 0) {
+        __redisSetError(c,REDIS_ERR_OTHER,strerror(errno));
+        return REDIS_ERR;
+    }
+
+    val = interval/3;
+    if (val == 0) val = 1;
+    if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPINTVL, &val, sizeof(val)) < 0) {
+        __redisSetError(c,REDIS_ERR_OTHER,strerror(errno));
+        return REDIS_ERR;
+    }
+
+    val = 3;
+    if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPCNT, &val, sizeof(val)) < 0) {
+        __redisSetError(c,REDIS_ERR_OTHER,strerror(errno));
+        return REDIS_ERR;
+    }
+#endif
+#endif
+
+    return REDIS_OK;
+}
+
+static int redisSetTcpNoDelay(redisContext *c) {
+    int yes = 1;
+    if (setsockopt(c->fd, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof(yes)) == -1) {
+        __redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(TCP_NODELAY)");
+        redisContextCloseFd(c);
+        return REDIS_ERR;
+    }
+    return REDIS_OK;
+}
+
+#define __MAX_MSEC (((LONG_MAX) - 999) / 1000)
+
+static int redisContextWaitReady(redisContext *c, const struct timeval *timeout) {
+    struct pollfd   wfd[1];
+    long msec;
+
+    msec          = -1;
+    wfd[0].fd     = c->fd;
+    wfd[0].events = POLLOUT;
+
+    /* Only use timeout when not NULL. */
+    if (timeout != NULL) {
+        if (timeout->tv_usec > 1000000 || timeout->tv_sec > __MAX_MSEC) {
+            __redisSetErrorFromErrno(c, REDIS_ERR_IO, NULL);
+            redisContextCloseFd(c);
+            return REDIS_ERR;
+        }
+
+        msec = (timeout->tv_sec * 1000) + ((timeout->tv_usec + 999) / 1000);
+
+        if (msec < 0 || msec > INT_MAX) {
+            msec = INT_MAX;
+        }
+    }
+
+    if (errno == EINPROGRESS) {
+        int res;
+
+        if ((res = poll(wfd, 1, msec)) == -1) {
+            __redisSetErrorFromErrno(c, REDIS_ERR_IO, "poll(2)");
+            redisContextCloseFd(c);
+            return REDIS_ERR;
+        } else if (res == 0) {
+            errno = ETIMEDOUT;
+            __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
+            redisContextCloseFd(c);
+            return REDIS_ERR;
+        }
+
+        if (redisCheckSocketError(c) != REDIS_OK)
+            return REDIS_ERR;
+
+        return REDIS_OK;
+    }
+
+    __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
+    redisContextCloseFd(c);
+    return REDIS_ERR;
+}
+
+int redisCheckSocketError(redisContext *c) {
+    int err = 0;
+    socklen_t errlen = sizeof(err);
+
+    if (getsockopt(c->fd, SOL_SOCKET, SO_ERROR, &err, &errlen) == -1) {
+        __redisSetErrorFromErrno(c,REDIS_ERR_IO,"getsockopt(SO_ERROR)");
+        return REDIS_ERR;
+    }
+
+    if (err) {
+        errno = err;
+        __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
+        return REDIS_ERR;
+    }
+
+    return REDIS_OK;
+}
+
+int redisContextSetTimeout(redisContext *c, const struct timeval tv) {
+    if (setsockopt(c->fd,SOL_SOCKET,SO_RCVTIMEO,&tv,sizeof(tv)) == -1) {
+        __redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(SO_RCVTIMEO)");
+        return REDIS_ERR;
+    }
+    if (setsockopt(c->fd,SOL_SOCKET,SO_SNDTIMEO,&tv,sizeof(tv)) == -1) {
+        __redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(SO_SNDTIMEO)");
+        return REDIS_ERR;
+    }
+    return REDIS_OK;
+}
+
+static int _redisContextConnectTcp(redisContext *c, const char *addr, int port,
+                                   const struct timeval *timeout,
+                                   const char *source_addr) {
+    int s, rv, n;
+    char _port[6];  /* strlen("65535"); */
+    struct addrinfo hints, *servinfo, *bservinfo, *p, *b;
+    int blocking = (c->flags & REDIS_BLOCK);
+    int reuseaddr = (c->flags & REDIS_REUSEADDR);
+    int reuses = 0;
+
+    c->connection_type = REDIS_CONN_TCP;
+    c->tcp.port = port;
+
+    /* We need to take possession of the passed parameters
+     * to make them reusable for a reconnect.
+     * We also carefully check we don't free data we already own,
+     * as in the case of the reconnect method.
+     *
+     * This is a bit ugly, but atleast it works and doesn't leak memory.
+     **/
+    if (c->tcp.host != addr) {
+        if (c->tcp.host)
+            free(c->tcp.host);
+
+        c->tcp.host = strdup(addr);
+    }
+
+    if (timeout) {
+        if (c->timeout != timeout) {
+            if (c->timeout == NULL)
+                c->timeout = malloc(sizeof(struct timeval));
+
+            memcpy(c->timeout, timeout, sizeof(struct timeval));
+        }
+    } else {
+        if (c->timeout)
+            free(c->timeout);
+        c->timeout = NULL;
+    }
+
+    if (source_addr == NULL) {
+        free(c->tcp.source_addr);
+        c->tcp.source_addr = NULL;
+    } else if (c->tcp.source_addr != source_addr) {
+        free(c->tcp.source_addr);
+        c->tcp.source_addr = strdup(source_addr);
+    }
+
+    snprintf(_port, 6, "%d", port);
+    memset(&hints,0,sizeof(hints));
+    hints.ai_family = AF_INET;
+    hints.ai_socktype = SOCK_STREAM;
+
+    /* Try with IPv6 if no IPv4 address was found. We do it in this order since
+     * in a Redis client you can't afford to test if you have IPv6 connectivity
+     * as this would add latency to every connect. Otherwise a more sensible
+     * route could be: Use IPv6 if both addresses are available and there is IPv6
+     * connectivity. */
+    if ((rv = getaddrinfo(c->tcp.host,_port,&hints,&servinfo)) != 0) {
+         hints.ai_family = AF_INET6;
+         if ((rv = getaddrinfo(addr,_port,&hints,&servinfo)) != 0) {
+            __redisSetError(c,REDIS_ERR_OTHER,gai_strerror(rv));
+            return REDIS_ERR;
+        }
+    }
+    for (p = servinfo; p != NULL; p = p->ai_next) {
+addrretry:
+        if ((s = socket(p->ai_family,p->ai_socktype,p->ai_protocol)) == -1)
+            continue;
+
+        c->fd = s;
+        if (redisSetBlocking(c,0) != REDIS_OK)
+            goto error;
+        if (c->tcp.source_addr) {
+            int bound = 0;
+            /* Using getaddrinfo saves us from self-determining IPv4 vs IPv6 */
+            if ((rv = getaddrinfo(c->tcp.source_addr, NULL, &hints, &bservinfo)) != 0) {
+                char buf[128];
+                snprintf(buf,sizeof(buf),"Can't get addr: %s",gai_strerror(rv));
+                __redisSetError(c,REDIS_ERR_OTHER,buf);
+                goto error;
+            }
+
+            if (reuseaddr) {
+                n = 1;
+                if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char*) &n,
+                               sizeof(n)) < 0) {
+                    goto error;
+                }
+            }
+
+            for (b = bservinfo; b != NULL; b = b->ai_next) {
+                if (bind(s,b->ai_addr,b->ai_addrlen) != -1) {
+                    bound = 1;
+                    break;
+                }
+            }
+            freeaddrinfo(bservinfo);
+            if (!bound) {
+                char buf[128];
+                snprintf(buf,sizeof(buf),"Can't bind socket: %s",strerror(errno));
+                __redisSetError(c,REDIS_ERR_OTHER,buf);
+                goto error;
+            }
+        }
+        if (connect(s,p->ai_addr,p->ai_addrlen) == -1) {
+            if (errno == EHOSTUNREACH) {
+                redisContextCloseFd(c);
+                continue;
+            } else if (errno == EINPROGRESS && !blocking) {
+                /* This is ok. */
+            } else if (errno == EADDRNOTAVAIL && reuseaddr) {
+                if (++reuses >= REDIS_CONNECT_RETRIES) {
+                    goto error;
+                } else {
+                    goto addrretry;
+                }
+            } else {
+                if (redisContextWaitReady(c,c->timeout) != REDIS_OK)
+                    goto error;
+            }
+        }
+        if (blocking && redisSetBlocking(c,1) != REDIS_OK)
+            goto error;
+        if (redisSetTcpNoDelay(c) != REDIS_OK)
+            goto error;
+
+        c->flags |= REDIS_CONNECTED;
+        rv = REDIS_OK;
+        goto end;
+    }
+    if (p == NULL) {
+        char buf[128];
+        snprintf(buf,sizeof(buf),"Can't create socket: %s",strerror(errno));
+        __redisSetError(c,REDIS_ERR_OTHER,buf);
+        goto error;
+    }
+
+error:
+    rv = REDIS_ERR;
+end:
+    freeaddrinfo(servinfo);
+    return rv;  // Need to return REDIS_OK if alright
+}
+
+int redisContextConnectTcp(redisContext *c, const char *addr, int port,
+                           const struct timeval *timeout) {
+    return _redisContextConnectTcp(c, addr, port, timeout, NULL);
+}
+
+int redisContextConnectBindTcp(redisContext *c, const char *addr, int port,
+                               const struct timeval *timeout,
+                               const char *source_addr) {
+    return _redisContextConnectTcp(c, addr, port, timeout, source_addr);
+}
+
+int redisContextConnectUnix(redisContext *c, const char *path, const struct timeval *timeout) {
+    int blocking = (c->flags & REDIS_BLOCK);
+    struct sockaddr_un sa;
+
+    if (redisCreateSocket(c,AF_LOCAL) < 0)
+        return REDIS_ERR;
+    if (redisSetBlocking(c,0) != REDIS_OK)
+        return REDIS_ERR;
+
+    c->connection_type = REDIS_CONN_UNIX;
+    if (c->unix_sock.path != path)
+        c->unix_sock.path = strdup(path);
+
+    if (timeout) {
+        if (c->timeout != timeout) {
+            if (c->timeout == NULL)
+                c->timeout = malloc(sizeof(struct timeval));
+
+            memcpy(c->timeout, timeout, sizeof(struct timeval));
+        }
+    } else {
+        if (c->timeout)
+            free(c->timeout);
+        c->timeout = NULL;
+    }
+
+    sa.sun_family = AF_LOCAL;
+    strncpy(sa.sun_path,path,sizeof(sa.sun_path)-1);
+    if (connect(c->fd, (struct sockaddr*)&sa, sizeof(sa)) == -1) {
+        if (errno == EINPROGRESS && !blocking) {
+            /* This is ok. */
+        } else {
+            if (redisContextWaitReady(c,c->timeout) != REDIS_OK)
+                return REDIS_ERR;
+        }
+    }
+
+    /* Reset socket to be blocking after connect(2). */
+    if (blocking && redisSetBlocking(c,1) != REDIS_OK)
+        return REDIS_ERR;
+
+    c->flags |= REDIS_CONNECTED;
+    return REDIS_OK;
+}

+ 53 - 0
ext/hiredis-vip-0.3.0/net.h

@@ -0,0 +1,53 @@
+/* Extracted from anet.c to work properly with Hiredis error reporting.
+ *
+ * Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com>
+ * Copyright (c) 2010-2014, Pieter Noordhuis <pcnoordhuis at gmail dot com>
+ * Copyright (c) 2015, Matt Stancliff <matt at genges dot com>,
+ *                     Jan-Erik Rediger <janerik at fnordig dot com>
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Redis nor the names of its contributors may be used
+ *     to endorse or promote products derived from this software without
+ *     specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __NET_H
+#define __NET_H
+
+#include "hiredis.h"
+
+#if defined(__sun)
+#define AF_LOCAL AF_UNIX
+#endif
+
+int redisCheckSocketError(redisContext *c);
+int redisContextSetTimeout(redisContext *c, const struct timeval tv);
+int redisContextConnectTcp(redisContext *c, const char *addr, int port, const struct timeval *timeout);
+int redisContextConnectBindTcp(redisContext *c, const char *addr, int port,
+                               const struct timeval *timeout,
+                               const char *source_addr);
+int redisContextConnectUnix(redisContext *c, const char *path, const struct timeval *timeout);
+int redisKeepAlive(redisContext *c, int interval);
+
+#endif

+ 525 - 0
ext/hiredis-vip-0.3.0/read.c

@@ -0,0 +1,525 @@
+/*
+ * Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com>
+ * Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com>
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Redis nor the names of its contributors may be used
+ *     to endorse or promote products derived from this software without
+ *     specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+
+#include "fmacros.h"
+#include <string.h>
+#include <stdlib.h>
+#ifndef _MSC_VER
+#include <unistd.h>
+#endif
+#include <assert.h>
+#include <errno.h>
+#include <ctype.h>
+
+#include "read.h"
+#include "sds.h"
+
+static void __redisReaderSetError(redisReader *r, int type, const char *str) {
+    size_t len;
+
+    if (r->reply != NULL && r->fn && r->fn->freeObject) {
+        r->fn->freeObject(r->reply);
+        r->reply = NULL;
+    }
+
+    /* Clear input buffer on errors. */
+    if (r->buf != NULL) {
+        sdsfree(r->buf);
+        r->buf = NULL;
+        r->pos = r->len = 0;
+    }
+
+    /* Reset task stack. */
+    r->ridx = -1;
+
+    /* Set error. */
+    r->err = type;
+    len = strlen(str);
+    len = len < (sizeof(r->errstr)-1) ? len : (sizeof(r->errstr)-1);
+    memcpy(r->errstr,str,len);
+    r->errstr[len] = '\0';
+}
+
+static size_t chrtos(char *buf, size_t size, char byte) {
+    size_t len = 0;
+
+    switch(byte) {
+    case '\\':
+    case '"':
+        len = snprintf(buf,size,"\"\\%c\"",byte);
+        break;
+    case '\n': len = snprintf(buf,size,"\"\\n\""); break;
+    case '\r': len = snprintf(buf,size,"\"\\r\""); break;
+    case '\t': len = snprintf(buf,size,"\"\\t\""); break;
+    case '\a': len = snprintf(buf,size,"\"\\a\""); break;
+    case '\b': len = snprintf(buf,size,"\"\\b\""); break;
+    default:
+        if (isprint(byte))
+            len = snprintf(buf,size,"\"%c\"",byte);
+        else
+            len = snprintf(buf,size,"\"\\x%02x\"",(unsigned char)byte);
+        break;
+    }
+
+    return len;
+}
+
+static void __redisReaderSetErrorProtocolByte(redisReader *r, char byte) {
+    char cbuf[8], sbuf[128];
+
+    chrtos(cbuf,sizeof(cbuf),byte);
+    snprintf(sbuf,sizeof(sbuf),
+        "Protocol error, got %s as reply type byte", cbuf);
+    __redisReaderSetError(r,REDIS_ERR_PROTOCOL,sbuf);
+}
+
+static void __redisReaderSetErrorOOM(redisReader *r) {
+    __redisReaderSetError(r,REDIS_ERR_OOM,"Out of memory");
+}
+
+static char *readBytes(redisReader *r, unsigned int bytes) {
+    char *p;
+    if (r->len-r->pos >= bytes) {
+        p = r->buf+r->pos;
+        r->pos += bytes;
+        return p;
+    }
+    return NULL;
+}
+
+/* Find pointer to \r\n. */
+static char *seekNewline(char *s, size_t len) {
+    int pos = 0;
+    int _len = len-1;
+
+    /* Position should be < len-1 because the character at "pos" should be
+     * followed by a \n. Note that strchr cannot be used because it doesn't
+     * allow to search a limited length and the buffer that is being searched
+     * might not have a trailing NULL character. */
+    while (pos < _len) {
+        while(pos < _len && s[pos] != '\r') pos++;
+        if (s[pos] != '\r') {
+            /* Not found. */
+            return NULL;
+        } else {
+            if (s[pos+1] == '\n') {
+                /* Found. */
+                return s+pos;
+            } else {
+                /* Continue searching. */
+                pos++;
+            }
+        }
+    }
+    return NULL;
+}
+
+/* Read a long long value starting at *s, under the assumption that it will be
+ * terminated by \r\n. Ambiguously returns -1 for unexpected input. */
+static long long readLongLong(char *s) {
+    long long v = 0;
+    int dec, mult = 1;
+    char c;
+
+    if (*s == '-') {
+        mult = -1;
+        s++;
+    } else if (*s == '+') {
+        mult = 1;
+        s++;
+    }
+
+    while ((c = *(s++)) != '\r') {
+        dec = c - '0';
+        if (dec >= 0 && dec < 10) {
+            v *= 10;
+            v += dec;
+        } else {
+            /* Should not happen... */
+            return -1;
+        }
+    }
+
+    return mult*v;
+}
+
+static char *readLine(redisReader *r, int *_len) {
+    char *p, *s;
+    int len;
+
+    p = r->buf+r->pos;
+    s = seekNewline(p,(r->len-r->pos));
+    if (s != NULL) {
+        len = s-(r->buf+r->pos);
+        r->pos += len+2; /* skip \r\n */
+        if (_len) *_len = len;
+        return p;
+    }
+    return NULL;
+}
+
+static void moveToNextTask(redisReader *r) {
+    redisReadTask *cur, *prv;
+    while (r->ridx >= 0) {
+        /* Return a.s.a.p. when the stack is now empty. */
+        if (r->ridx == 0) {
+            r->ridx--;
+            return;
+        }
+
+        cur = &(r->rstack[r->ridx]);
+        prv = &(r->rstack[r->ridx-1]);
+        assert(prv->type == REDIS_REPLY_ARRAY);
+        if (cur->idx == prv->elements-1) {
+            r->ridx--;
+        } else {
+            /* Reset the type because the next item can be anything */
+            assert(cur->idx < prv->elements);
+            cur->type = -1;
+            cur->elements = -1;
+            cur->idx++;
+            return;
+        }
+    }
+}
+
+static int processLineItem(redisReader *r) {
+    redisReadTask *cur = &(r->rstack[r->ridx]);
+    void *obj;
+    char *p;
+    int len;
+
+    if ((p = readLine(r,&len)) != NULL) {
+        if (cur->type == REDIS_REPLY_INTEGER) {
+            if (r->fn && r->fn->createInteger)
+                obj = r->fn->createInteger(cur,readLongLong(p));
+            else
+                obj = (void*)REDIS_REPLY_INTEGER;
+        } else {
+            /* Type will be error or status. */
+            if (r->fn && r->fn->createString)
+                obj = r->fn->createString(cur,p,len);
+            else
+                obj = (void*)(size_t)(cur->type);
+        }
+
+        if (obj == NULL) {
+            __redisReaderSetErrorOOM(r);
+            return REDIS_ERR;
+        }
+
+        /* Set reply if this is the root object. */
+        if (r->ridx == 0) r->reply = obj;
+        moveToNextTask(r);
+        return REDIS_OK;
+    }
+
+    return REDIS_ERR;
+}
+
+static int processBulkItem(redisReader *r) {
+    redisReadTask *cur = &(r->rstack[r->ridx]);
+    void *obj = NULL;
+    char *p, *s;
+    long len;
+    unsigned long bytelen;
+    int success = 0;
+
+    p = r->buf+r->pos;
+    s = seekNewline(p,r->len-r->pos);
+    if (s != NULL) {
+        p = r->buf+r->pos;
+        bytelen = s-(r->buf+r->pos)+2; /* include \r\n */
+        len = readLongLong(p);
+
+        if (len < 0) {
+            /* The nil object can always be created. */
+            if (r->fn && r->fn->createNil)
+                obj = r->fn->createNil(cur);
+            else
+                obj = (void*)REDIS_REPLY_NIL;
+            success = 1;
+        } else {
+            /* Only continue when the buffer contains the entire bulk item. */
+            bytelen += len+2; /* include \r\n */
+            if (r->pos+bytelen <= r->len) {
+                if (r->fn && r->fn->createString)
+                    obj = r->fn->createString(cur,s+2,len);
+                else
+                    obj = (void*)REDIS_REPLY_STRING;
+                success = 1;
+            }
+        }
+
+        /* Proceed when obj was created. */
+        if (success) {
+            if (obj == NULL) {
+                __redisReaderSetErrorOOM(r);
+                return REDIS_ERR;
+            }
+
+            r->pos += bytelen;
+
+            /* Set reply if this is the root object. */
+            if (r->ridx == 0) r->reply = obj;
+            moveToNextTask(r);
+            return REDIS_OK;
+        }
+    }
+
+    return REDIS_ERR;
+}
+
+static int processMultiBulkItem(redisReader *r) {
+    redisReadTask *cur = &(r->rstack[r->ridx]);
+    void *obj;
+    char *p;
+    long elements;
+    int root = 0;
+
+    /* Set error for nested multi bulks with depth > 7 */
+    if (r->ridx == 8) {
+        __redisReaderSetError(r,REDIS_ERR_PROTOCOL,
+            "No support for nested multi bulk replies with depth > 7");
+        return REDIS_ERR;
+    }
+
+    if ((p = readLine(r,NULL)) != NULL) {
+        elements = readLongLong(p);
+        root = (r->ridx == 0);
+
+        if (elements == -1) {
+            if (r->fn && r->fn->createNil)
+                obj = r->fn->createNil(cur);
+            else
+                obj = (void*)REDIS_REPLY_NIL;
+
+            if (obj == NULL) {
+                __redisReaderSetErrorOOM(r);
+                return REDIS_ERR;
+            }
+
+            moveToNextTask(r);
+        } else {
+            if (r->fn && r->fn->createArray)
+                obj = r->fn->createArray(cur,elements);
+            else
+                obj = (void*)REDIS_REPLY_ARRAY;
+
+            if (obj == NULL) {
+                __redisReaderSetErrorOOM(r);
+                return REDIS_ERR;
+            }
+
+            /* Modify task stack when there are more than 0 elements. */
+            if (elements > 0) {
+                cur->elements = elements;
+                cur->obj = obj;
+                r->ridx++;
+                r->rstack[r->ridx].type = -1;
+                r->rstack[r->ridx].elements = -1;
+                r->rstack[r->ridx].idx = 0;
+                r->rstack[r->ridx].obj = NULL;
+                r->rstack[r->ridx].parent = cur;
+                r->rstack[r->ridx].privdata = r->privdata;
+            } else {
+                moveToNextTask(r);
+            }
+        }
+
+        /* Set reply if this is the root object. */
+        if (root) r->reply = obj;
+        return REDIS_OK;
+    }
+
+    return REDIS_ERR;
+}
+
+static int processItem(redisReader *r) {
+    redisReadTask *cur = &(r->rstack[r->ridx]);
+    char *p;
+
+    /* check if we need to read type */
+    if (cur->type < 0) {
+        if ((p = readBytes(r,1)) != NULL) {
+            switch (p[0]) {
+            case '-':
+                cur->type = REDIS_REPLY_ERROR;
+                break;
+            case '+':
+                cur->type = REDIS_REPLY_STATUS;
+                break;
+            case ':':
+                cur->type = REDIS_REPLY_INTEGER;
+                break;
+            case '$':
+                cur->type = REDIS_REPLY_STRING;
+                break;
+            case '*':
+                cur->type = REDIS_REPLY_ARRAY;
+                break;
+            default:
+                __redisReaderSetErrorProtocolByte(r,*p);
+                return REDIS_ERR;
+            }
+        } else {
+            /* could not consume 1 byte */
+            return REDIS_ERR;
+        }
+    }
+
+    /* process typed item */
+    switch(cur->type) {
+    case REDIS_REPLY_ERROR:
+    case REDIS_REPLY_STATUS:
+    case REDIS_REPLY_INTEGER:
+        return processLineItem(r);
+    case REDIS_REPLY_STRING:
+        return processBulkItem(r);
+    case REDIS_REPLY_ARRAY:
+        return processMultiBulkItem(r);
+    default:
+        assert(NULL);
+        return REDIS_ERR; /* Avoid warning. */
+    }
+}
+
+redisReader *redisReaderCreateWithFunctions(redisReplyObjectFunctions *fn) {
+    redisReader *r;
+
+    r = calloc(sizeof(redisReader),1);
+    if (r == NULL)
+        return NULL;
+
+    r->err = 0;
+    r->errstr[0] = '\0';
+    r->fn = fn;
+    r->buf = sdsempty();
+    r->maxbuf = REDIS_READER_MAX_BUF;
+    if (r->buf == NULL) {
+        free(r);
+        return NULL;
+    }
+
+    r->ridx = -1;
+    return r;
+}
+
+void redisReaderFree(redisReader *r) {
+    if (r->reply != NULL && r->fn && r->fn->freeObject)
+        r->fn->freeObject(r->reply);
+    if (r->buf != NULL)
+        sdsfree(r->buf);
+    free(r);
+}
+
+int redisReaderFeed(redisReader *r, const char *buf, size_t len) {
+    sds newbuf;
+
+    /* Return early when this reader is in an erroneous state. */
+    if (r->err)
+        return REDIS_ERR;
+
+    /* Copy the provided buffer. */
+    if (buf != NULL && len >= 1) {
+        /* Destroy internal buffer when it is empty and is quite large. */
+        if (r->len == 0 && r->maxbuf != 0 && sdsavail(r->buf) > r->maxbuf) {
+            sdsfree(r->buf);
+            r->buf = sdsempty();
+            r->pos = 0;
+
+            /* r->buf should not be NULL since we just free'd a larger one. */
+            assert(r->buf != NULL);
+        }
+
+        newbuf = sdscatlen(r->buf,buf,len);
+        if (newbuf == NULL) {
+            __redisReaderSetErrorOOM(r);
+            return REDIS_ERR;
+        }
+
+        r->buf = newbuf;
+        r->len = sdslen(r->buf);
+    }
+
+    return REDIS_OK;
+}
+
+int redisReaderGetReply(redisReader *r, void **reply) {
+    /* Default target pointer to NULL. */
+    if (reply != NULL)
+        *reply = NULL;
+
+    /* Return early when this reader is in an erroneous state. */
+    if (r->err)
+        return REDIS_ERR;
+
+    /* When the buffer is empty, there will never be a reply. */
+    if (r->len == 0)
+        return REDIS_OK;
+
+    /* Set first item to process when the stack is empty. */
+    if (r->ridx == -1) {
+        r->rstack[0].type = -1;
+        r->rstack[0].elements = -1;
+        r->rstack[0].idx = -1;
+        r->rstack[0].obj = NULL;
+        r->rstack[0].parent = NULL;
+        r->rstack[0].privdata = r->privdata;
+        r->ridx = 0;
+    }
+
+    /* Process items in reply. */
+    while (r->ridx >= 0)
+        if (processItem(r) != REDIS_OK)
+            break;
+
+    /* Return ASAP when an error occurred. */
+    if (r->err)
+        return REDIS_ERR;
+
+    /* Discard part of the buffer when we've consumed at least 1k, to avoid
+     * doing unnecessary calls to memmove() in sds.c. */
+    if (r->pos >= 1024) {
+        sdsrange(r->buf,r->pos,-1);
+        r->pos = 0;
+        r->len = sdslen(r->buf);
+    }
+
+    /* Emit a reply when there is one. */
+    if (r->ridx == -1) {
+        if (reply != NULL)
+            *reply = r->reply;
+        r->reply = NULL;
+    }
+    return REDIS_OK;
+}

+ 129 - 0
ext/hiredis-vip-0.3.0/read.h

@@ -0,0 +1,129 @@
+/*
+ * Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com>
+ * Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com>
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Redis nor the names of its contributors may be used
+ *     to endorse or promote products derived from this software without
+ *     specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+
+#ifndef __HIREDIS_READ_H
+#define __HIREDIS_READ_H
+#include <stdio.h> /* for size_t */
+
+#define REDIS_ERR -1
+#define REDIS_OK 0
+
+/* When an error occurs, the err flag in a context is set to hold the type of
+ * error that occured. REDIS_ERR_IO means there was an I/O error and you
+ * should use the "errno" variable to find out what is wrong.
+ * For other values, the "errstr" field will hold a description. */
+#define REDIS_ERR_IO 1 /* Error in read or write */
+#define REDIS_ERR_EOF 3 /* End of file */
+#define REDIS_ERR_PROTOCOL 4 /* Protocol error */
+#define REDIS_ERR_OOM 5 /* Out of memory */
+#define REDIS_ERR_OTHER 2 /* Everything else... */
+#if 1 //shenzheng 2015-8-10 redis cluster
+#define REDIS_ERR_CLUSTER_TOO_MANY_REDIRECT 6
+#endif //shenzheng 2015-8-10 redis cluster
+
+#define REDIS_REPLY_STRING 1
+#define REDIS_REPLY_ARRAY 2
+#define REDIS_REPLY_INTEGER 3
+#define REDIS_REPLY_NIL 4
+#define REDIS_REPLY_STATUS 5
+#define REDIS_REPLY_ERROR 6
+
+#define REDIS_READER_MAX_BUF (1024*16)  /* Default max unused reader buffer. */
+
+#if 1 //shenzheng 2015-8-22 redis cluster
+#define REDIS_ERROR_MOVED 			"MOVED"
+#define REDIS_ERROR_ASK 			"ASK"
+#define REDIS_ERROR_TRYAGAIN 		"TRYAGAIN"
+#define REDIS_ERROR_CROSSSLOT 		"CROSSSLOT"
+#define REDIS_ERROR_CLUSTERDOWN 	"CLUSTERDOWN"
+
+#define REDIS_STATUS_OK 			"OK"
+#endif //shenzheng 2015-9-24 redis cluster
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct redisReadTask {
+    int type;
+    int elements; /* number of elements in multibulk container */
+    int idx; /* index in parent (array) object */
+    void *obj; /* holds user-generated value for a read task */
+    struct redisReadTask *parent; /* parent task */
+    void *privdata; /* user-settable arbitrary field */
+} redisReadTask;
+
+typedef struct redisReplyObjectFunctions {
+    void *(*createString)(const redisReadTask*, char*, size_t);
+    void *(*createArray)(const redisReadTask*, int);
+    void *(*createInteger)(const redisReadTask*, long long);
+    void *(*createNil)(const redisReadTask*);
+    void (*freeObject)(void*);
+} redisReplyObjectFunctions;
+
+typedef struct redisReader {
+    int err; /* Error flags, 0 when there is no error */
+    char errstr[128]; /* String representation of error when applicable */
+
+    char *buf; /* Read buffer */
+    size_t pos; /* Buffer cursor */
+    size_t len; /* Buffer length */
+    size_t maxbuf; /* Max length of unused buffer */
+
+    redisReadTask rstack[9];
+    int ridx; /* Index of current read task */
+    void *reply; /* Temporary reply pointer */
+
+    redisReplyObjectFunctions *fn;
+    void *privdata;
+} redisReader;
+
+/* Public API for the protocol parser. */
+redisReader *redisReaderCreateWithFunctions(redisReplyObjectFunctions *fn);
+void redisReaderFree(redisReader *r);
+int redisReaderFeed(redisReader *r, const char *buf, size_t len);
+int redisReaderGetReply(redisReader *r, void **reply);
+
+/* Backwards compatibility, can be removed on big version bump. */
+#define redisReplyReaderCreate redisReaderCreate
+#define redisReplyReaderFree redisReaderFree
+#define redisReplyReaderFeed redisReaderFeed
+#define redisReplyReaderGetReply redisReaderGetReply
+#define redisReplyReaderSetPrivdata(_r, _p) (int)(((redisReader*)(_r))->privdata = (_p))
+#define redisReplyReaderGetObject(_r) (((redisReader*)(_r))->reply)
+#define redisReplyReaderGetError(_r) (((redisReader*)(_r))->errstr)
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif

+ 1095 - 0
ext/hiredis-vip-0.3.0/sds.c

@@ -0,0 +1,1095 @@
+/* SDS (Simple Dynamic Strings), A C dynamic strings library.
+ *
+ * Copyright (c) 2006-2014, Salvatore Sanfilippo <antirez at gmail dot com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Redis nor the names of its contributors may be used
+ *     to endorse or promote products derived from this software without
+ *     specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <assert.h>
+
+#include "sds.h"
+
+/* 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 sds strings are, always) so
+ * even if you create an sds string with:
+ *
+ * mystring = sdsnewlen("abc",3");
+ *
+ * You can print the string with printf() as there is an implicit \0 at the
+ * end of the string. However the string is binary safe and can contain
+ * \0 characters in the middle, as the length is stored in the sds header. */
+sds sdsnewlen(const void *init, size_t initlen) {
+    struct sdshdr *sh;
+
+    if (init) {
+        sh = malloc(sizeof *sh+initlen+1);
+    } else {
+        sh = calloc(sizeof *sh+initlen+1,1);
+    }
+    if (sh == NULL) return NULL;
+    sh->len = initlen;
+    sh->free = 0;
+    if (initlen && init)
+        memcpy(sh->buf, init, initlen);
+    sh->buf[initlen] = '\0';
+    return (char*)sh->buf;
+}
+
+/* Create an empty (zero length) sds string. Even in this case the string
+ * always has an implicit null term. */
+sds sdsempty(void) {
+    return sdsnewlen("",0);
+}
+
+/* Create a new sds string starting from a null termined C string. */
+sds sdsnew(const char *init) {
+    size_t initlen = (init == NULL) ? 0 : strlen(init);
+    return sdsnewlen(init, initlen);
+}
+
+/* Duplicate an sds string. */
+sds sdsdup(const sds s) {
+    return sdsnewlen(s, sdslen(s));
+}
+
+/* Free an sds string. No operation is performed if 's' is NULL. */
+void sdsfree(sds s) {
+    if (s == NULL) return;
+    free(s-sizeof(struct sdshdr));
+}
+
+/* Set the sds string length to the length as obtained with strlen(), so
+ * considering as content only up to the first null term character.
+ *
+ * This function is useful when the sds string is hacked manually in some
+ * way, like in the following example:
+ *
+ * s = sdsnew("foobar");
+ * s[2] = '\0';
+ * sdsupdatelen(s);
+ * printf("%d\n", sdslen(s));
+ *
+ * The output will be "2", but if we comment out the call to sdsupdatelen()
+ * the output will be "6" as the string was modified but the logical length
+ * remains 6 bytes. */
+void sdsupdatelen(sds s) {
+    struct sdshdr *sh = (void*) (s-sizeof *sh);
+    int reallen = strlen(s);
+    sh->free += (sh->len-reallen);
+    sh->len = reallen;
+}
+
+/* Modify an sds string on-place to make it empty (zero length).
+ * However all the existing buffer is not discarded but set as free space
+ * so that next append operations will not require allocations up to the
+ * number of bytes previously available. */
+void sdsclear(sds s) {
+    struct sdshdr *sh = (void*) (s-sizeof *sh);
+    sh->free += sh->len;
+    sh->len = 0;
+    sh->buf[0] = '\0';
+}
+
+/* Enlarge the free space at the end of the sds string so that the caller
+ * is sure that after calling this function can overwrite up to addlen
+ * bytes after the end of the string, plus one more byte for nul term.
+ *
+ * Note: this does not change the *length* of the sds string as returned
+ * by sdslen(), but only the free buffer space we have. */
+sds sdsMakeRoomFor(sds s, size_t addlen) {
+    struct sdshdr *sh, *newsh;
+    size_t free = sdsavail(s);
+    size_t len, newlen;
+
+    if (free >= addlen) return s;
+    len = sdslen(s);
+    sh = (void*) (s-sizeof *sh);
+    newlen = (len+addlen);
+    if (newlen < SDS_MAX_PREALLOC)
+        newlen *= 2;
+    else
+        newlen += SDS_MAX_PREALLOC;
+    newsh = realloc(sh, sizeof *newsh+newlen+1);
+    if (newsh == NULL) return NULL;
+
+    newsh->free = newlen - len;
+    return newsh->buf;
+}
+
+/* Reallocate the sds string so that it has no free space at the end. The
+ * contained string remains not altered, but next concatenation operations
+ * will require a reallocation.
+ *
+ * After the call, the passed sds string is no longer valid and all the
+ * references must be substituted with the new pointer returned by the call. */
+sds sdsRemoveFreeSpace(sds s) {
+    struct sdshdr *sh;
+
+    sh = (void*) (s-sizeof *sh);
+    sh = realloc(sh, sizeof *sh+sh->len+1);
+    sh->free = 0;
+    return sh->buf;
+}
+
+/* Return the total size of the allocation of the specifed sds string,
+ * including:
+ * 1) The sds header before the pointer.
+ * 2) The string.
+ * 3) The free buffer at the end if any.
+ * 4) The implicit null term.
+ */
+size_t sdsAllocSize(sds s) {
+    struct sdshdr *sh = (void*) (s-sizeof *sh);
+
+    return sizeof(*sh)+sh->len+sh->free+1;
+}
+
+/* Increment the sds length and decrements the left free space at the
+ * end of the string according to 'incr'. Also set the null term
+ * in the new end of the string.
+ *
+ * This function is used in order to fix the string length after the
+ * user calls sdsMakeRoomFor(), writes something after the end of
+ * the current string, and finally needs to set the new length.
+ *
+ * Note: it is possible to use a negative increment in order to
+ * right-trim the string.
+ *
+ * Usage example:
+ *
+ * Using sdsIncrLen() and sdsMakeRoomFor() it is possible to mount the
+ * following schema, to cat bytes coming from the kernel to the end of an
+ * sds string without copying into an intermediate buffer:
+ *
+ * oldlen = sdslen(s);
+ * s = sdsMakeRoomFor(s, BUFFER_SIZE);
+ * nread = read(fd, s+oldlen, BUFFER_SIZE);
+ * ... check for nread <= 0 and handle it ...
+ * sdsIncrLen(s, nread);
+ */
+void sdsIncrLen(sds s, int incr) {
+    struct sdshdr *sh = (void*) (s-sizeof *sh);
+
+    assert(sh->free >= incr);
+    sh->len += incr;
+    sh->free -= incr;
+    assert(sh->free >= 0);
+    s[sh->len] = '\0';
+}
+
+/* Grow the sds to have the specified length. Bytes that were not part of
+ * the original length of the sds will be set to zero.
+ *
+ * if the specified length is smaller than the current length, no operation
+ * is performed. */
+sds sdsgrowzero(sds s, size_t len) {
+    struct sdshdr *sh = (void*) (s-sizeof *sh);
+    size_t totlen, curlen = sh->len;
+
+    if (len <= curlen) return s;
+    s = sdsMakeRoomFor(s,len-curlen);
+    if (s == NULL) return NULL;
+
+    /* Make sure added region doesn't contain garbage */
+    sh = (void*)(s-sizeof *sh);
+    memset(s+curlen,0,(len-curlen+1)); /* also set trailing \0 byte */
+    totlen = sh->len+sh->free;
+    sh->len = len;
+    sh->free = totlen-sh->len;
+    return s;
+}
+
+/* Append the specified binary-safe string pointed by 't' of 'len' bytes to the
+ * end of the specified sds string 's'.
+ *
+ * After the call, the passed sds string is no longer valid and all the
+ * references must be substituted with the new pointer returned by the call. */
+sds sdscatlen(sds s, const void *t, size_t len) {
+    struct sdshdr *sh;
+    size_t curlen = sdslen(s);
+
+    s = sdsMakeRoomFor(s,len);
+    if (s == NULL) return NULL;
+    sh = (void*) (s-sizeof *sh);
+    memcpy(s+curlen, t, len);
+    sh->len = curlen+len;
+    sh->free = sh->free-len;
+    s[curlen+len] = '\0';
+    return s;
+}
+
+/* Append the specified null termianted C string to the sds string 's'.
+ *
+ * After the call, the passed sds string is no longer valid and all the
+ * references must be substituted with the new pointer returned by the call. */
+sds sdscat(sds s, const char *t) {
+    return sdscatlen(s, t, strlen(t));
+}
+
+/* Append the specified sds 't' to the existing sds 's'.
+ *
+ * After the call, the modified sds string is no longer valid and all the
+ * references must be substituted with the new pointer returned by the call. */
+sds sdscatsds(sds s, const sds t) {
+    return sdscatlen(s, t, sdslen(t));
+}
+
+/* Destructively modify the sds string 's' to hold the specified binary
+ * safe string pointed by 't' of length 'len' bytes. */
+sds sdscpylen(sds s, const char *t, size_t len) {
+    struct sdshdr *sh = (void*) (s-sizeof *sh);
+    size_t totlen = sh->free+sh->len;
+
+    if (totlen < len) {
+        s = sdsMakeRoomFor(s,len-sh->len);
+        if (s == NULL) return NULL;
+        sh = (void*) (s-sizeof *sh);
+        totlen = sh->free+sh->len;
+    }
+    memcpy(s, t, len);
+    s[len] = '\0';
+    sh->len = len;
+    sh->free = totlen-len;
+    return s;
+}
+
+/* Like sdscpylen() but 't' must be a null-termined string so that the length
+ * of the string is obtained with strlen(). */
+sds sdscpy(sds s, const char *t) {
+    return sdscpylen(s, t, strlen(t));
+}
+
+/* Helper for sdscatlonglong() doing the actual number -> string
+ * conversion. 's' must point to a string with room for at least
+ * SDS_LLSTR_SIZE bytes.
+ *
+ * The function returns the lenght of the null-terminated string
+ * representation stored at 's'. */
+#define SDS_LLSTR_SIZE 21
+int sdsll2str(char *s, long long value) {
+    char *p, aux;
+    unsigned long long v;
+    size_t l;
+
+    /* Generate the string representation, this method produces
+     * an reversed string. */
+    v = (value < 0) ? -value : value;
+    p = s;
+    do {
+        *p++ = '0'+(v%10);
+        v /= 10;
+    } while(v);
+    if (value < 0) *p++ = '-';
+
+    /* Compute length and add null term. */
+    l = p-s;
+    *p = '\0';
+
+    /* Reverse the string. */
+    p--;
+    while(s < p) {
+        aux = *s;
+        *s = *p;
+        *p = aux;
+        s++;
+        p--;
+    }
+    return l;
+}
+
+/* Identical sdsll2str(), but for unsigned long long type. */
+int sdsull2str(char *s, unsigned long long v) {
+    char *p, aux;
+    size_t l;
+
+    /* Generate the string representation, this method produces
+     * an reversed string. */
+    p = s;
+    do {
+        *p++ = '0'+(v%10);
+        v /= 10;
+    } while(v);
+
+    /* Compute length and add null term. */
+    l = p-s;
+    *p = '\0';
+
+    /* Reverse the string. */
+    p--;
+    while(s < p) {
+        aux = *s;
+        *s = *p;
+        *p = aux;
+        s++;
+        p--;
+    }
+    return l;
+}
+
+/* Like sdscatpritf() but gets va_list instead of being variadic. */
+sds sdscatvprintf(sds s, const char *fmt, va_list ap) {
+    va_list cpy;
+    char *buf, *t;
+    size_t buflen = 16;
+
+    while(1) {
+        buf = malloc(buflen);
+        if (buf == NULL) return NULL;
+        buf[buflen-2] = '\0';
+        va_copy(cpy,ap);
+        vsnprintf(buf, buflen, fmt, cpy);
+        if (buf[buflen-2] != '\0') {
+            free(buf);
+            buflen *= 2;
+            continue;
+        }
+        break;
+    }
+    t = sdscat(s, buf);
+    free(buf);
+    return t;
+}
+
+/* Append to the sds string 's' a string obtained using printf-alike format
+ * specifier.
+ *
+ * After the call, the modified sds string is no longer valid and all the
+ * references must be substituted with the new pointer returned by the call.
+ *
+ * Example:
+ *
+ * s = sdsnew("Sum is: ");
+ * s = sdscatprintf(s,"%d+%d = %d",a,b,a+b);
+ *
+ * Often you need to create a string from scratch with the printf-alike
+ * format. When this is the need, just use sdsempty() as the target string:
+ *
+ * s = sdscatprintf(sdsempty(), "... your format ...", args);
+ */
+sds sdscatprintf(sds s, const char *fmt, ...) {
+    va_list ap;
+    char *t;
+    va_start(ap, fmt);
+    t = sdscatvprintf(s,fmt,ap);
+    va_end(ap);
+    return t;
+}
+
+/* This function is similar to sdscatprintf, but much faster as it does
+ * not rely on sprintf() family functions implemented by the libc that
+ * are often very slow. Moreover directly handling the sds string as
+ * new data is concatenated provides a performance improvement.
+ *
+ * However this function only handles an incompatible subset of printf-alike
+ * format specifiers:
+ *
+ * %s - C String
+ * %S - SDS string
+ * %i - signed int
+ * %I - 64 bit signed integer (long long, int64_t)
+ * %u - unsigned int
+ * %U - 64 bit unsigned integer (unsigned long long, uint64_t)
+ * %T - A size_t variable.
+ * %% - Verbatim "%" character.
+ */
+sds sdscatfmt(sds s, char const *fmt, ...) {
+    struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
+    size_t initlen = sdslen(s);
+    const char *f = fmt;
+    int i;
+    va_list ap;
+
+    va_start(ap,fmt);
+    f = fmt;    /* Next format specifier byte to process. */
+    i = initlen; /* Position of the next byte to write to dest str. */
+    while(*f) {
+        char next, *str;
+        int l;
+        long long num;
+        unsigned long long unum;
+
+        /* Make sure there is always space for at least 1 char. */
+        if (sh->free == 0) {
+            s = sdsMakeRoomFor(s,1);
+            sh = (void*) (s-(sizeof(struct sdshdr)));
+        }
+
+        switch(*f) {
+        case '%':
+            next = *(f+1);
+            f++;
+            switch(next) {
+            case 's':
+            case 'S':
+                str = va_arg(ap,char*);
+                l = (next == 's') ? strlen(str) : sdslen(str);
+                if (sh->free < l) {
+                    s = sdsMakeRoomFor(s,l);
+                    sh = (void*) (s-(sizeof(struct sdshdr)));
+                }
+                memcpy(s+i,str,l);
+                sh->len += l;
+                sh->free -= l;
+                i += l;
+                break;
+            case 'i':
+            case 'I':
+                if (next == 'i')
+                    num = va_arg(ap,int);
+                else
+                    num = va_arg(ap,long long);
+                {
+                    char buf[SDS_LLSTR_SIZE];
+                    l = sdsll2str(buf,num);
+                    if (sh->free < l) {
+                        s = sdsMakeRoomFor(s,l);
+                        sh = (void*) (s-(sizeof(struct sdshdr)));
+                    }
+                    memcpy(s+i,buf,l);
+                    sh->len += l;
+                    sh->free -= l;
+                    i += l;
+                }
+                break;
+            case 'u':
+            case 'U':
+            case 'T':
+                if (next == 'u')
+                    unum = va_arg(ap,unsigned int);
+                else if(next == 'U')
+                    unum = va_arg(ap,unsigned long long);
+                else
+                    unum = (unsigned long long)va_arg(ap,size_t);
+                {
+                    char buf[SDS_LLSTR_SIZE];
+                    l = sdsull2str(buf,unum);
+                    if (sh->free < l) {
+                        s = sdsMakeRoomFor(s,l);
+                        sh = (void*) (s-(sizeof(struct sdshdr)));
+                    }
+                    memcpy(s+i,buf,l);
+                    sh->len += l;
+                    sh->free -= l;
+                    i += l;
+                }
+                break;
+            default: /* Handle %% and generally %<unknown>. */
+                s[i++] = next;
+                sh->len += 1;
+                sh->free -= 1;
+                break;
+            }
+            break;
+        default:
+            s[i++] = *f;
+            sh->len += 1;
+            sh->free -= 1;
+            break;
+        }
+        f++;
+    }
+    va_end(ap);
+
+    /* Add null-term */
+    s[i] = '\0';
+    return s;
+}
+
+
+/* Remove the part of the string from left and from right composed just of
+ * contiguous characters found in 'cset', that is a null terminted C string.
+ *
+ * After the call, the modified sds string is no longer valid and all the
+ * references must be substituted with the new pointer returned by the call.
+ *
+ * Example:
+ *
+ * s = sdsnew("AA...AA.a.aa.aHelloWorld     :::");
+ * s = sdstrim(s,"A. :");
+ * printf("%s\n", s);
+ *
+ * Output will be just "Hello World".
+ */
+void sdstrim(sds s, const char *cset) {
+    struct sdshdr *sh = (void*) (s-sizeof *sh);
+    char *start, *end, *sp, *ep;
+    size_t len;
+
+    sp = start = s;
+    ep = end = s+sdslen(s)-1;
+    while(sp <= end && strchr(cset, *sp)) sp++;
+    while(ep > start && strchr(cset, *ep)) ep--;
+    len = (sp > ep) ? 0 : ((ep-sp)+1);
+    if (sh->buf != sp) memmove(sh->buf, sp, len);
+    sh->buf[len] = '\0';
+    sh->free = sh->free+(sh->len-len);
+    sh->len = len;
+}
+
+/* Turn the string into a smaller (or equal) string containing only the
+ * substring specified by the 'start' and 'end' indexes.
+ *
+ * start and end can be negative, where -1 means the last character of the
+ * string, -2 the penultimate character, and so forth.
+ *
+ * The interval is inclusive, so the start and end characters will be part
+ * of the resulting string.
+ *
+ * The string is modified in-place.
+ *
+ * Example:
+ *
+ * s = sdsnew("Hello World");
+ * sdsrange(s,1,-1); => "ello World"
+ */
+void sdsrange(sds s, int start, int end) {
+    struct sdshdr *sh = (void*) (s-sizeof *sh);
+    size_t newlen, len = sdslen(s);
+
+    if (len == 0) return;
+    if (start < 0) {
+        start = len+start;
+        if (start < 0) start = 0;
+    }
+    if (end < 0) {
+        end = len+end;
+        if (end < 0) end = 0;
+    }
+    newlen = (start > end) ? 0 : (end-start)+1;
+    if (newlen != 0) {
+        if (start >= (signed)len) {
+            newlen = 0;
+        } else if (end >= (signed)len) {
+            end = len-1;
+            newlen = (start > end) ? 0 : (end-start)+1;
+        }
+    } else {
+        start = 0;
+    }
+    if (start && newlen) memmove(sh->buf, sh->buf+start, newlen);
+    sh->buf[newlen] = 0;
+    sh->free = sh->free+(sh->len-newlen);
+    sh->len = newlen;
+}
+
+/* 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 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 sds strings s1 and s2 with memcmp().
+ *
+ * Return value:
+ *
+ *     1 if s1 > s2.
+ *    -1 if s1 < s2.
+ *     0 if s1 and s2 are exactly the same binary string.
+ *
+ * If two strings share exactly the same prefix, but one of the two has
+ * additional characters, the longer string is considered to be greater than
+ * the smaller one. */
+int sdscmp(const sds s1, const sds s2) {
+    size_t l1, l2, minlen;
+    int cmp;
+
+    l1 = sdslen(s1);
+    l2 = sdslen(s2);
+    minlen = (l1 < l2) ? l1 : l2;
+    cmp = memcmp(s1,s2,minlen);
+    if (cmp == 0) return l1-l2;
+    return cmp;
+}
+
+/* Split 's' with separator in 'sep'. An array
+ * of sds strings is returned. *count will be set
+ * by reference to the number of tokens returned.
+ *
+ * On out of memory, zero length string, zero length
+ * separator, NULL is returned.
+ *
+ * Note that 'sep' is able to split a string using
+ * a multi-character separator. For example
+ * sdssplit("foo_-_bar","_-_"); will return two
+ * elements "foo" and "bar".
+ *
+ * This version of the function is binary-safe but
+ * requires length arguments. sdssplit() is just the
+ * same function but for zero-terminated strings.
+ */
+sds *sdssplitlen(const char *s, int len, const char *sep, int seplen, int *count) {
+    int elements = 0, slots = 5, start = 0, j;
+    sds *tokens;
+
+    if (seplen < 1 || len < 0) return NULL;
+
+    tokens = malloc(sizeof(sds)*slots);
+    if (tokens == NULL) return NULL;
+
+    if (len == 0) {
+        *count = 0;
+        return tokens;
+    }
+    for (j = 0; j < (len-(seplen-1)); j++) {
+        /* make sure there is room for the next element and the final one */
+        if (slots < elements+2) {
+            sds *newtokens;
+
+            slots *= 2;
+            newtokens = realloc(tokens,sizeof(sds)*slots);
+            if (newtokens == NULL) goto cleanup;
+            tokens = newtokens;
+        }
+        /* search the separator */
+        if ((seplen == 1 && *(s+j) == sep[0]) || (memcmp(s+j,sep,seplen) == 0)) {
+            tokens[elements] = sdsnewlen(s+start,j-start);
+            if (tokens[elements] == NULL) goto cleanup;
+            elements++;
+            start = j+seplen;
+            j = j+seplen-1; /* skip the separator */
+        }
+    }
+    /* Add the final element. We are sure there is room in the tokens array. */
+    tokens[elements] = sdsnewlen(s+start,len-start);
+    if (tokens[elements] == NULL) goto cleanup;
+    elements++;
+    *count = elements;
+    return tokens;
+
+cleanup:
+    {
+        int i;
+        for (i = 0; i < elements; i++) sdsfree(tokens[i]);
+        free(tokens);
+        *count = 0;
+        return NULL;
+    }
+}
+
+/* Free the result returned by sdssplitlen(), or do nothing if 'tokens' is NULL. */
+void sdsfreesplitres(sds *tokens, int count) {
+    if (!tokens) return;
+    while(count--)
+        sdsfree(tokens[count]);
+    free(tokens);
+}
+
+/* Create an sds string from a long long value. It is much faster than:
+ *
+ * sdscatprintf(sdsempty(),"%lld\n", value);
+ */
+sds sdsfromlonglong(long long value) {
+    char buf[32], *p;
+    unsigned long long v;
+
+    v = (value < 0) ? -value : value;
+    p = buf+31; /* point to the last character */
+    do {
+        *p-- = '0'+(v%10);
+        v /= 10;
+    } while(v);
+    if (value < 0) *p-- = '-';
+    p++;
+    return sdsnewlen(p,32-(p-buf));
+}
+
+/* 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<hex-number>".
+ *
+ * After the call, the modified sds string is no longer valid and all the
+ * references must be substituted with the new pointer returned by the call. */
+sds sdscatrepr(sds s, const char *p, size_t len) {
+    s = sdscatlen(s,"\"",1);
+    while(len--) {
+        switch(*p) {
+        case '\\':
+        case '"':
+            s = sdscatprintf(s,"\\%c",*p);
+            break;
+        case '\n': s = sdscatlen(s,"\\n",2); break;
+        case '\r': s = sdscatlen(s,"\\r",2); break;
+        case '\t': s = sdscatlen(s,"\\t",2); break;
+        case '\a': s = sdscatlen(s,"\\a",2); break;
+        case '\b': s = sdscatlen(s,"\\b",2); break;
+        default:
+            if (isprint(*p))
+                s = sdscatprintf(s,"%c",*p);
+            else
+                s = sdscatprintf(s,"\\x%02x",(unsigned char)*p);
+            break;
+        }
+        p++;
+    }
+    return sdscatlen(s,"\"",1);
+}
+
+/* Helper function for sdssplitargs() that returns non zero if 'c'
+ * is a valid hex digit. */
+int is_hex_digit(char c) {
+    return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') ||
+           (c >= 'A' && c <= 'F');
+}
+
+/* Helper function for sdssplitargs() that converts a hex digit into an
+ * integer from 0 to 15 */
+int hex_digit_to_int(char c) {
+    switch(c) {
+    case '0': return 0;
+    case '1': return 1;
+    case '2': return 2;
+    case '3': return 3;
+    case '4': return 4;
+    case '5': return 5;
+    case '6': return 6;
+    case '7': return 7;
+    case '8': return 8;
+    case '9': return 9;
+    case 'a': case 'A': return 10;
+    case 'b': case 'B': return 11;
+    case 'c': case 'C': return 12;
+    case 'd': case 'D': return 13;
+    case 'e': case 'E': return 14;
+    case 'f': case 'F': return 15;
+    default: return 0;
+    }
+}
+
+/* Split a line into arguments, where every argument can be in the
+ * following programming-language REPL-alike form:
+ *
+ * foo bar "newline are supported\n" and "\xff\x00otherstuff"
+ *
+ * The number of arguments is stored into *argc, and an array
+ * of sds is returned.
+ *
+ * The caller should free the resulting array of sds strings with
+ * sdsfreesplitres().
+ *
+ * Note that sdscatrepr() is able to convert back a string into
+ * a quoted string in the same format sdssplitargs() is able to parse.
+ *
+ * The function returns the allocated tokens on success, even when the
+ * input string is empty, or NULL if the input contains unbalanced
+ * quotes or closed quotes followed by non space characters
+ * as in: "foo"bar or "foo'
+ */
+sds *sdssplitargs(const char *line, int *argc) {
+    const char *p = line;
+    char *current = NULL;
+    char **vector = NULL;
+
+    *argc = 0;
+    while(1) {
+        /* skip blanks */
+        while(*p && isspace(*p)) p++;
+        if (*p) {
+            /* get a token */
+            int inq=0;  /* set to 1 if we are in "quotes" */
+            int insq=0; /* set to 1 if we are in 'single quotes' */
+            int done=0;
+
+            if (current == NULL) current = sdsempty();
+            while(!done) {
+                if (inq) {
+                    if (*p == '\\' && *(p+1) == 'x' &&
+                                             is_hex_digit(*(p+2)) &&
+                                             is_hex_digit(*(p+3)))
+                    {
+                        unsigned char byte;
+
+                        byte = (hex_digit_to_int(*(p+2))*16)+
+                                hex_digit_to_int(*(p+3));
+                        current = sdscatlen(current,(char*)&byte,1);
+                        p += 3;
+                    } else if (*p == '\\' && *(p+1)) {
+                        char c;
+
+                        p++;
+                        switch(*p) {
+                        case 'n': c = '\n'; break;
+                        case 'r': c = '\r'; break;
+                        case 't': c = '\t'; break;
+                        case 'b': c = '\b'; break;
+                        case 'a': c = '\a'; break;
+                        default: c = *p; break;
+                        }
+                        current = sdscatlen(current,&c,1);
+                    } else if (*p == '"') {
+                        /* closing quote must be followed by a space or
+                         * nothing at all. */
+                        if (*(p+1) && !isspace(*(p+1))) goto err;
+                        done=1;
+                    } else if (!*p) {
+                        /* unterminated quotes */
+                        goto err;
+                    } else {
+                        current = sdscatlen(current,p,1);
+                    }
+                } else if (insq) {
+                    if (*p == '\\' && *(p+1) == '\'') {
+                        p++;
+                        current = sdscatlen(current,"'",1);
+                    } else if (*p == '\'') {
+                        /* closing quote must be followed by a space or
+                         * nothing at all. */
+                        if (*(p+1) && !isspace(*(p+1))) goto err;
+                        done=1;
+                    } else if (!*p) {
+                        /* unterminated quotes */
+                        goto err;
+                    } else {
+                        current = sdscatlen(current,p,1);
+                    }
+                } else {
+                    switch(*p) {
+                    case ' ':
+                    case '\n':
+                    case '\r':
+                    case '\t':
+                    case '\0':
+                        done=1;
+                        break;
+                    case '"':
+                        inq=1;
+                        break;
+                    case '\'':
+                        insq=1;
+                        break;
+                    default:
+                        current = sdscatlen(current,p,1);
+                        break;
+                    }
+                }
+                if (*p) p++;
+            }
+            /* add the token to the vector */
+            vector = realloc(vector,((*argc)+1)*sizeof(char*));
+            vector[*argc] = current;
+            (*argc)++;
+            current = NULL;
+        } else {
+            /* Even on empty input string return something not NULL. */
+            if (vector == NULL) vector = malloc(sizeof(void*));
+            return vector;
+        }
+    }
+
+err:
+    while((*argc)--)
+        sdsfree(vector[*argc]);
+    free(vector);
+    if (current) sdsfree(current);
+    *argc = 0;
+    return NULL;
+}
+
+/* Modify the string substituting all the occurrences of the set of
+ * characters specified in the 'from' string to the corresponding character
+ * in the 'to' array.
+ *
+ * For instance: sdsmapchars(mystring, "ho", "01", 2)
+ * will have the effect of turning the string "hello" into "0ell1".
+ *
+ * The function returns the sds string pointer, that is always the same
+ * as the input pointer since no resize is needed. */
+sds sdsmapchars(sds s, const char *from, const char *to, size_t setlen) {
+    size_t j, i, l = sdslen(s);
+
+    for (j = 0; j < l; j++) {
+        for (i = 0; i < setlen; i++) {
+            if (s[j] == from[i]) {
+                s[j] = to[i];
+                break;
+            }
+        }
+    }
+    return s;
+}
+
+/* Join an array of C strings using the specified separator (also a C string).
+ * Returns the result as an sds string. */
+sds sdsjoin(char **argv, int argc, char *sep, size_t seplen) {
+    sds join = sdsempty();
+    int j;
+
+    for (j = 0; j < argc; j++) {
+        join = sdscat(join, argv[j]);
+        if (j != argc-1) join = sdscatlen(join,sep,seplen);
+    }
+    return join;
+}
+
+/* Like sdsjoin, but joins an array of SDS strings. */
+sds sdsjoinsds(sds *argv, int argc, const char *sep, size_t seplen) {
+    sds join = sdsempty();
+    int j;
+
+    for (j = 0; j < argc; j++) {
+        join = sdscatsds(join, argv[j]);
+        if (j != argc-1) join = sdscatlen(join,sep,seplen);
+    }
+    return join;
+}
+
+#ifdef SDS_TEST_MAIN
+#include <stdio.h>
+#include "testhelp.h"
+
+int main(void) {
+    {
+        struct sdshdr *sh;
+        sds x = sdsnew("foo"), y;
+
+        test_cond("Create a string and obtain the length",
+            sdslen(x) == 3 && memcmp(x,"foo\0",4) == 0)
+
+        sdsfree(x);
+        x = sdsnewlen("foo",2);
+        test_cond("Create a string with specified length",
+            sdslen(x) == 2 && memcmp(x,"fo\0",3) == 0)
+
+        x = sdscat(x,"bar");
+        test_cond("Strings concatenation",
+            sdslen(x) == 5 && memcmp(x,"fobar\0",6) == 0);
+
+        x = sdscpy(x,"a");
+        test_cond("sdscpy() against an originally longer string",
+            sdslen(x) == 1 && memcmp(x,"a\0",2) == 0)
+
+        x = sdscpy(x,"xyzxxxxxxxxxxyyyyyyyyyykkkkkkkkkk");
+        test_cond("sdscpy() against an originally shorter string",
+            sdslen(x) == 33 &&
+            memcmp(x,"xyzxxxxxxxxxxyyyyyyyyyykkkkkkkkkk\0",33) == 0)
+
+        sdsfree(x);
+        x = sdscatprintf(sdsempty(),"%d",123);
+        test_cond("sdscatprintf() seems working in the base case",
+            sdslen(x) == 3 && memcmp(x,"123\0",4) ==0)
+
+        sdsfree(x);
+        x = sdsnew("xxciaoyyy");
+        sdstrim(x,"xy");
+        test_cond("sdstrim() correctly trims characters",
+            sdslen(x) == 4 && memcmp(x,"ciao\0",5) == 0)
+
+        y = sdsdup(x);
+        sdsrange(y,1,1);
+        test_cond("sdsrange(...,1,1)",
+            sdslen(y) == 1 && memcmp(y,"i\0",2) == 0)
+
+        sdsfree(y);
+        y = sdsdup(x);
+        sdsrange(y,1,-1);
+        test_cond("sdsrange(...,1,-1)",
+            sdslen(y) == 3 && memcmp(y,"iao\0",4) == 0)
+
+        sdsfree(y);
+        y = sdsdup(x);
+        sdsrange(y,-2,-1);
+        test_cond("sdsrange(...,-2,-1)",
+            sdslen(y) == 2 && memcmp(y,"ao\0",3) == 0)
+
+        sdsfree(y);
+        y = sdsdup(x);
+        sdsrange(y,2,1);
+        test_cond("sdsrange(...,2,1)",
+            sdslen(y) == 0 && memcmp(y,"\0",1) == 0)
+
+        sdsfree(y);
+        y = sdsdup(x);
+        sdsrange(y,1,100);
+        test_cond("sdsrange(...,1,100)",
+            sdslen(y) == 3 && memcmp(y,"iao\0",4) == 0)
+
+        sdsfree(y);
+        y = sdsdup(x);
+        sdsrange(y,100,100);
+        test_cond("sdsrange(...,100,100)",
+            sdslen(y) == 0 && memcmp(y,"\0",1) == 0)
+
+        sdsfree(y);
+        sdsfree(x);
+        x = sdsnew("foo");
+        y = sdsnew("foa");
+        test_cond("sdscmp(foo,foa)", sdscmp(x,y) > 0)
+
+        sdsfree(y);
+        sdsfree(x);
+        x = sdsnew("bar");
+        y = sdsnew("bar");
+        test_cond("sdscmp(bar,bar)", sdscmp(x,y) == 0)
+
+        sdsfree(y);
+        sdsfree(x);
+        x = sdsnew("aar");
+        y = sdsnew("bar");
+        test_cond("sdscmp(bar,bar)", sdscmp(x,y) < 0)
+
+        sdsfree(y);
+        sdsfree(x);
+        x = sdsnewlen("\a\n\0foo\r",7);
+        y = sdscatrepr(sdsempty(),x,sdslen(x));
+        test_cond("sdscatrepr(...data...)",
+            memcmp(y,"\"\\a\\n\\x00foo\\r\"",15) == 0)
+
+        {
+            int oldfree;
+
+            sdsfree(x);
+            x = sdsnew("0");
+            sh = (void*) (x-(sizeof(struct sdshdr)));
+            test_cond("sdsnew() free/len buffers", sh->len == 1 && sh->free == 0);
+            x = sdsMakeRoomFor(x,1);
+            sh = (void*) (x-(sizeof(struct sdshdr)));
+            test_cond("sdsMakeRoomFor()", sh->len == 1 && sh->free > 0);
+            oldfree = sh->free;
+            x[1] = '1';
+            sdsIncrLen(x,1);
+            test_cond("sdsIncrLen() -- content", x[0] == '0' && x[1] == '1');
+            test_cond("sdsIncrLen() -- len", sh->len == 2);
+            test_cond("sdsIncrLen() -- free", sh->free == oldfree-1);
+        }
+    }
+    test_report()
+    return 0;
+}
+#endif

+ 105 - 0
ext/hiredis-vip-0.3.0/sds.h

@@ -0,0 +1,105 @@
+/* SDS (Simple Dynamic Strings), A C dynamic strings library.
+ *
+ * Copyright (c) 2006-2014, Salvatore Sanfilippo <antirez at gmail dot com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Redis nor the names of its contributors may be used
+ *     to endorse or promote products derived from this software without
+ *     specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __SDS_H
+#define __SDS_H
+
+#define SDS_MAX_PREALLOC (1024*1024)
+
+#include <sys/types.h>
+#include <stdarg.h>
+#ifdef _MSC_VER
+#include "win32.h"
+#endif
+
+typedef char *sds;
+
+struct sdshdr {
+    int len;
+    int free;
+    char buf[];
+};
+
+static inline size_t sdslen(const sds s) {
+    struct sdshdr *sh = (struct sdshdr *)(s-sizeof *sh);
+    return sh->len;
+}
+
+static inline size_t sdsavail(const sds s) {
+    struct sdshdr *sh = (struct sdshdr *)(s-sizeof *sh);
+    return sh->free;
+}
+
+sds sdsnewlen(const void *init, size_t initlen);
+sds sdsnew(const char *init);
+sds sdsempty(void);
+size_t sdslen(const sds s);
+sds sdsdup(const sds s);
+void sdsfree(sds s);
+size_t sdsavail(const sds s);
+sds sdsgrowzero(sds s, size_t len);
+sds sdscatlen(sds s, const void *t, size_t len);
+sds sdscat(sds s, const char *t);
+sds sdscatsds(sds s, const sds t);
+sds sdscpylen(sds s, const char *t, size_t len);
+sds sdscpy(sds s, const char *t);
+
+sds sdscatvprintf(sds s, const char *fmt, va_list ap);
+#ifdef __GNUC__
+sds sdscatprintf(sds s, const char *fmt, ...)
+    __attribute__((format(printf, 2, 3)));
+#else
+sds sdscatprintf(sds s, const char *fmt, ...);
+#endif
+
+sds sdscatfmt(sds s, char const *fmt, ...);
+void sdstrim(sds s, const char *cset);
+void sdsrange(sds s, int start, int 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, size_t seplen);
+sds sdsjoinsds(sds *argv, int argc, const char *sep, size_t seplen);
+
+/* Low level functions exposed to the user API */
+sds sdsMakeRoomFor(sds s, size_t addlen);
+void sdsIncrLen(sds s, int incr);
+sds sdsRemoveFreeSpace(sds s);
+size_t sdsAllocSize(sds s);
+
+#endif

+ 806 - 0
ext/hiredis-vip-0.3.0/test.c

@@ -0,0 +1,806 @@
+#include "fmacros.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <sys/time.h>
+#include <assert.h>
+#include <unistd.h>
+#include <signal.h>
+#include <errno.h>
+#include <limits.h>
+
+#include "hiredis.h"
+#include "net.h"
+
+enum connection_type {
+    CONN_TCP,
+    CONN_UNIX,
+    CONN_FD
+};
+
+struct config {
+    enum connection_type type;
+
+    struct {
+        const char *host;
+        int port;
+        struct timeval timeout;
+    } tcp;
+
+    struct {
+        const char *path;
+    } unix;
+};
+
+/* The following lines make up our testing "framework" :) */
+static int tests = 0, fails = 0;
+#define test(_s) { printf("#%02d ", ++tests); printf(_s); }
+#define test_cond(_c) if(_c) printf("\033[0;32mPASSED\033[0;0m\n"); else {printf("\033[0;31mFAILED\033[0;0m\n"); fails++;}
+
+static long long usec(void) {
+    struct timeval tv;
+    gettimeofday(&tv,NULL);
+    return (((long long)tv.tv_sec)*1000000)+tv.tv_usec;
+}
+
+/* The assert() calls below have side effects, so we need assert()
+ * even if we are compiling without asserts (-DNDEBUG). */
+#ifdef NDEBUG
+#undef assert
+#define assert(e) (void)(e)
+#endif
+
+static redisContext *select_database(redisContext *c) {
+    redisReply *reply;
+
+    /* Switch to DB 9 for testing, now that we know we can chat. */
+    reply = redisCommand(c,"SELECT 9");
+    assert(reply != NULL);
+    freeReplyObject(reply);
+
+    /* Make sure the DB is emtpy */
+    reply = redisCommand(c,"DBSIZE");
+    assert(reply != NULL);
+    if (reply->type == REDIS_REPLY_INTEGER && reply->integer == 0) {
+        /* Awesome, DB 9 is empty and we can continue. */
+        freeReplyObject(reply);
+    } else {
+        printf("Database #9 is not empty, test can not continue\n");
+        exit(1);
+    }
+
+    return c;
+}
+
+static int disconnect(redisContext *c, int keep_fd) {
+    redisReply *reply;
+
+    /* Make sure we're on DB 9. */
+    reply = redisCommand(c,"SELECT 9");
+    assert(reply != NULL);
+    freeReplyObject(reply);
+    reply = redisCommand(c,"FLUSHDB");
+    assert(reply != NULL);
+    freeReplyObject(reply);
+
+    /* Free the context as well, but keep the fd if requested. */
+    if (keep_fd)
+        return redisFreeKeepFd(c);
+    redisFree(c);
+    return -1;
+}
+
+static redisContext *connect(struct config config) {
+    redisContext *c = NULL;
+
+    if (config.type == CONN_TCP) {
+        c = redisConnect(config.tcp.host, config.tcp.port);
+    } else if (config.type == CONN_UNIX) {
+        c = redisConnectUnix(config.unix.path);
+    } else if (config.type == CONN_FD) {
+        /* Create a dummy connection just to get an fd to inherit */
+        redisContext *dummy_ctx = redisConnectUnix(config.unix.path);
+        if (dummy_ctx) {
+            int fd = disconnect(dummy_ctx, 1);
+            printf("Connecting to inherited fd %d\n", fd);
+            c = redisConnectFd(fd);
+        }
+    } else {
+        assert(NULL);
+    }
+
+    if (c == NULL) {
+        printf("Connection error: can't allocate redis context\n");
+        exit(1);
+    } else if (c->err) {
+        printf("Connection error: %s\n", c->errstr);
+        redisFree(c);
+        exit(1);
+    }
+
+    return select_database(c);
+}
+
+static void test_format_commands(void) {
+    char *cmd;
+    int len;
+
+    test("Format command without interpolation: ");
+    len = redisFormatCommand(&cmd,"SET foo bar");
+    test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 &&
+        len == 4+4+(3+2)+4+(3+2)+4+(3+2));
+    free(cmd);
+
+    test("Format command with %%s string interpolation: ");
+    len = redisFormatCommand(&cmd,"SET %s %s","foo","bar");
+    test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 &&
+        len == 4+4+(3+2)+4+(3+2)+4+(3+2));
+    free(cmd);
+
+    test("Format command with %%s and an empty string: ");
+    len = redisFormatCommand(&cmd,"SET %s %s","foo","");
+    test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$0\r\n\r\n",len) == 0 &&
+        len == 4+4+(3+2)+4+(3+2)+4+(0+2));
+    free(cmd);
+
+    test("Format command with an empty string in between proper interpolations: ");
+    len = redisFormatCommand(&cmd,"SET %s %s","","foo");
+    test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$0\r\n\r\n$3\r\nfoo\r\n",len) == 0 &&
+        len == 4+4+(3+2)+4+(0+2)+4+(3+2));
+    free(cmd);
+
+    test("Format command with %%b string interpolation: ");
+    len = redisFormatCommand(&cmd,"SET %b %b","foo",(size_t)3,"b\0r",(size_t)3);
+    test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nb\0r\r\n",len) == 0 &&
+        len == 4+4+(3+2)+4+(3+2)+4+(3+2));
+    free(cmd);
+
+    test("Format command with %%b and an empty string: ");
+    len = redisFormatCommand(&cmd,"SET %b %b","foo",(size_t)3,"",(size_t)0);
+    test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$0\r\n\r\n",len) == 0 &&
+        len == 4+4+(3+2)+4+(3+2)+4+(0+2));
+    free(cmd);
+
+    test("Format command with literal %%: ");
+    len = redisFormatCommand(&cmd,"SET %% %%");
+    test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$1\r\n%\r\n$1\r\n%\r\n",len) == 0 &&
+        len == 4+4+(3+2)+4+(1+2)+4+(1+2));
+    free(cmd);
+
+    /* Vararg width depends on the type. These tests make sure that the
+     * width is correctly determined using the format and subsequent varargs
+     * can correctly be interpolated. */
+#define INTEGER_WIDTH_TEST(fmt, type) do {                                                \
+    type value = 123;                                                                     \
+    test("Format command with printf-delegation (" #type "): ");                          \
+    len = redisFormatCommand(&cmd,"key:%08" fmt " str:%s", value, "hello");               \
+    test_cond(strncmp(cmd,"*2\r\n$12\r\nkey:00000123\r\n$9\r\nstr:hello\r\n",len) == 0 && \
+        len == 4+5+(12+2)+4+(9+2));                                                       \
+    free(cmd);                                                                            \
+} while(0)
+
+#define FLOAT_WIDTH_TEST(type) do {                                                       \
+    type value = 123.0;                                                                   \
+    test("Format command with printf-delegation (" #type "): ");                          \
+    len = redisFormatCommand(&cmd,"key:%08.3f str:%s", value, "hello");                   \
+    test_cond(strncmp(cmd,"*2\r\n$12\r\nkey:0123.000\r\n$9\r\nstr:hello\r\n",len) == 0 && \
+        len == 4+5+(12+2)+4+(9+2));                                                       \
+    free(cmd);                                                                            \
+} while(0)
+
+    INTEGER_WIDTH_TEST("d", int);
+    INTEGER_WIDTH_TEST("hhd", char);
+    INTEGER_WIDTH_TEST("hd", short);
+    INTEGER_WIDTH_TEST("ld", long);
+    INTEGER_WIDTH_TEST("lld", long long);
+    INTEGER_WIDTH_TEST("u", unsigned int);
+    INTEGER_WIDTH_TEST("hhu", unsigned char);
+    INTEGER_WIDTH_TEST("hu", unsigned short);
+    INTEGER_WIDTH_TEST("lu", unsigned long);
+    INTEGER_WIDTH_TEST("llu", unsigned long long);
+    FLOAT_WIDTH_TEST(float);
+    FLOAT_WIDTH_TEST(double);
+
+    test("Format command with invalid printf format: ");
+    len = redisFormatCommand(&cmd,"key:%08p %b",(void*)1234,"foo",(size_t)3);
+    test_cond(len == -1);
+
+    const char *argv[3];
+    argv[0] = "SET";
+    argv[1] = "foo\0xxx";
+    argv[2] = "bar";
+    size_t lens[3] = { 3, 7, 3 };
+    int argc = 3;
+
+    test("Format command by passing argc/argv without lengths: ");
+    len = redisFormatCommandArgv(&cmd,argc,argv,NULL);
+    test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 &&
+        len == 4+4+(3+2)+4+(3+2)+4+(3+2));
+    free(cmd);
+
+    test("Format command by passing argc/argv with lengths: ");
+    len = redisFormatCommandArgv(&cmd,argc,argv,lens);
+    test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$7\r\nfoo\0xxx\r\n$3\r\nbar\r\n",len) == 0 &&
+        len == 4+4+(3+2)+4+(7+2)+4+(3+2));
+    free(cmd);
+}
+
+static void test_append_formatted_commands(struct config config) {
+    redisContext *c;
+    redisReply *reply;
+    char *cmd;
+    int len;
+
+    c = connect(config);
+
+    test("Append format command: ");
+
+    len = redisFormatCommand(&cmd, "SET foo bar");
+
+    test_cond(redisAppendFormattedCommand(c, cmd, len) == REDIS_OK);
+
+    assert(redisGetReply(c, (void*)&reply) == REDIS_OK);
+
+    free(cmd);
+    freeReplyObject(reply);
+
+    disconnect(c, 0);
+}
+
+static void test_reply_reader(void) {
+    redisReader *reader;
+    void *reply;
+    int ret;
+    int i;
+
+    test("Error handling in reply parser: ");
+    reader = redisReaderCreate();
+    redisReaderFeed(reader,(char*)"@foo\r\n",6);
+    ret = redisReaderGetReply(reader,NULL);
+    test_cond(ret == REDIS_ERR &&
+              strcasecmp(reader->errstr,"Protocol error, got \"@\" as reply type byte") == 0);
+    redisReaderFree(reader);
+
+    /* when the reply already contains multiple items, they must be free'd
+     * on an error. valgrind will bark when this doesn't happen. */
+    test("Memory cleanup in reply parser: ");
+    reader = redisReaderCreate();
+    redisReaderFeed(reader,(char*)"*2\r\n",4);
+    redisReaderFeed(reader,(char*)"$5\r\nhello\r\n",11);
+    redisReaderFeed(reader,(char*)"@foo\r\n",6);
+    ret = redisReaderGetReply(reader,NULL);
+    test_cond(ret == REDIS_ERR &&
+              strcasecmp(reader->errstr,"Protocol error, got \"@\" as reply type byte") == 0);
+    redisReaderFree(reader);
+
+    test("Set error on nested multi bulks with depth > 7: ");
+    reader = redisReaderCreate();
+
+    for (i = 0; i < 9; i++) {
+        redisReaderFeed(reader,(char*)"*1\r\n",4);
+    }
+
+    ret = redisReaderGetReply(reader,NULL);
+    test_cond(ret == REDIS_ERR &&
+              strncasecmp(reader->errstr,"No support for",14) == 0);
+    redisReaderFree(reader);
+
+    test("Works with NULL functions for reply: ");
+    reader = redisReaderCreate();
+    reader->fn = NULL;
+    redisReaderFeed(reader,(char*)"+OK\r\n",5);
+    ret = redisReaderGetReply(reader,&reply);
+    test_cond(ret == REDIS_OK && reply == (void*)REDIS_REPLY_STATUS);
+    redisReaderFree(reader);
+
+    test("Works when a single newline (\\r\\n) covers two calls to feed: ");
+    reader = redisReaderCreate();
+    reader->fn = NULL;
+    redisReaderFeed(reader,(char*)"+OK\r",4);
+    ret = redisReaderGetReply(reader,&reply);
+    assert(ret == REDIS_OK && reply == NULL);
+    redisReaderFeed(reader,(char*)"\n",1);
+    ret = redisReaderGetReply(reader,&reply);
+    test_cond(ret == REDIS_OK && reply == (void*)REDIS_REPLY_STATUS);
+    redisReaderFree(reader);
+
+    test("Don't reset state after protocol error: ");
+    reader = redisReaderCreate();
+    reader->fn = NULL;
+    redisReaderFeed(reader,(char*)"x",1);
+    ret = redisReaderGetReply(reader,&reply);
+    assert(ret == REDIS_ERR);
+    ret = redisReaderGetReply(reader,&reply);
+    test_cond(ret == REDIS_ERR && reply == NULL);
+    redisReaderFree(reader);
+
+    /* Regression test for issue #45 on GitHub. */
+    test("Don't do empty allocation for empty multi bulk: ");
+    reader = redisReaderCreate();
+    redisReaderFeed(reader,(char*)"*0\r\n",4);
+    ret = redisReaderGetReply(reader,&reply);
+    test_cond(ret == REDIS_OK &&
+        ((redisReply*)reply)->type == REDIS_REPLY_ARRAY &&
+        ((redisReply*)reply)->elements == 0);
+    freeReplyObject(reply);
+    redisReaderFree(reader);
+}
+
+static void test_free_null(void) {
+    void *redisContext = NULL;
+    void *reply = NULL;
+
+    test("Don't fail when redisFree is passed a NULL value: ");
+    redisFree(redisContext);
+    test_cond(redisContext == NULL);
+
+    test("Don't fail when freeReplyObject is passed a NULL value: ");
+    freeReplyObject(reply);
+    test_cond(reply == NULL);
+}
+
+static void test_blocking_connection_errors(void) {
+    redisContext *c;
+
+    test("Returns error when host cannot be resolved: ");
+    c = redisConnect((char*)"idontexist.test", 6379);
+    test_cond(c->err == REDIS_ERR_OTHER &&
+        (strcmp(c->errstr,"Name or service not known") == 0 ||
+         strcmp(c->errstr,"Can't resolve: idontexist.test") == 0 ||
+         strcmp(c->errstr,"nodename nor servname provided, or not known") == 0 ||
+         strcmp(c->errstr,"No address associated with hostname") == 0 ||
+         strcmp(c->errstr,"Temporary failure in name resolution") == 0 ||
+         strcmp(c->errstr,"no address associated with name") == 0));
+    redisFree(c);
+
+    test("Returns error when the port is not open: ");
+    c = redisConnect((char*)"localhost", 1);
+    test_cond(c->err == REDIS_ERR_IO &&
+        strcmp(c->errstr,"Connection refused") == 0);
+    redisFree(c);
+
+    test("Returns error when the unix socket path doesn't accept connections: ");
+    c = redisConnectUnix((char*)"/tmp/idontexist.sock");
+    test_cond(c->err == REDIS_ERR_IO); /* Don't care about the message... */
+    redisFree(c);
+}
+
+static void test_blocking_connection(struct config config) {
+    redisContext *c;
+    redisReply *reply;
+
+    c = connect(config);
+
+    test("Is able to deliver commands: ");
+    reply = redisCommand(c,"PING");
+    test_cond(reply->type == REDIS_REPLY_STATUS &&
+        strcasecmp(reply->str,"pong") == 0)
+    freeReplyObject(reply);
+
+    test("Is a able to send commands verbatim: ");
+    reply = redisCommand(c,"SET foo bar");
+    test_cond (reply->type == REDIS_REPLY_STATUS &&
+        strcasecmp(reply->str,"ok") == 0)
+    freeReplyObject(reply);
+
+    test("%%s String interpolation works: ");
+    reply = redisCommand(c,"SET %s %s","foo","hello world");
+    freeReplyObject(reply);
+    reply = redisCommand(c,"GET foo");
+    test_cond(reply->type == REDIS_REPLY_STRING &&
+        strcmp(reply->str,"hello world") == 0);
+    freeReplyObject(reply);
+
+    test("%%b String interpolation works: ");
+    reply = redisCommand(c,"SET %b %b","foo",(size_t)3,"hello\x00world",(size_t)11);
+    freeReplyObject(reply);
+    reply = redisCommand(c,"GET foo");
+    test_cond(reply->type == REDIS_REPLY_STRING &&
+        memcmp(reply->str,"hello\x00world",11) == 0)
+
+    test("Binary reply length is correct: ");
+    test_cond(reply->len == 11)
+    freeReplyObject(reply);
+
+    test("Can parse nil replies: ");
+    reply = redisCommand(c,"GET nokey");
+    test_cond(reply->type == REDIS_REPLY_NIL)
+    freeReplyObject(reply);
+
+    /* test 7 */
+    test("Can parse integer replies: ");
+    reply = redisCommand(c,"INCR mycounter");
+    test_cond(reply->type == REDIS_REPLY_INTEGER && reply->integer == 1)
+    freeReplyObject(reply);
+
+    test("Can parse multi bulk replies: ");
+    freeReplyObject(redisCommand(c,"LPUSH mylist foo"));
+    freeReplyObject(redisCommand(c,"LPUSH mylist bar"));
+    reply = redisCommand(c,"LRANGE mylist 0 -1");
+    test_cond(reply->type == REDIS_REPLY_ARRAY &&
+              reply->elements == 2 &&
+              !memcmp(reply->element[0]->str,"bar",3) &&
+              !memcmp(reply->element[1]->str,"foo",3))
+    freeReplyObject(reply);
+
+    /* m/e with multi bulk reply *before* other reply.
+     * specifically test ordering of reply items to parse. */
+    test("Can handle nested multi bulk replies: ");
+    freeReplyObject(redisCommand(c,"MULTI"));
+    freeReplyObject(redisCommand(c,"LRANGE mylist 0 -1"));
+    freeReplyObject(redisCommand(c,"PING"));
+    reply = (redisCommand(c,"EXEC"));
+    test_cond(reply->type == REDIS_REPLY_ARRAY &&
+              reply->elements == 2 &&
+              reply->element[0]->type == REDIS_REPLY_ARRAY &&
+              reply->element[0]->elements == 2 &&
+              !memcmp(reply->element[0]->element[0]->str,"bar",3) &&
+              !memcmp(reply->element[0]->element[1]->str,"foo",3) &&
+              reply->element[1]->type == REDIS_REPLY_STATUS &&
+              strcasecmp(reply->element[1]->str,"pong") == 0);
+    freeReplyObject(reply);
+
+    disconnect(c, 0);
+}
+
+static void test_blocking_connection_timeouts(struct config config) {
+    redisContext *c;
+    redisReply *reply;
+    ssize_t s;
+    const char *cmd = "DEBUG SLEEP 3\r\n";
+    struct timeval tv;
+
+    c = connect(config);
+    test("Successfully completes a command when the timeout is not exceeded: ");
+    reply = redisCommand(c,"SET foo fast");
+    freeReplyObject(reply);
+    tv.tv_sec = 0;
+    tv.tv_usec = 10000;
+    redisSetTimeout(c, tv);
+    reply = redisCommand(c, "GET foo");
+    test_cond(reply != NULL && reply->type == REDIS_REPLY_STRING && memcmp(reply->str, "fast", 4) == 0);
+    freeReplyObject(reply);
+    disconnect(c, 0);
+
+    c = connect(config);
+    test("Does not return a reply when the command times out: ");
+    s = write(c->fd, cmd, strlen(cmd));
+    tv.tv_sec = 0;
+    tv.tv_usec = 10000;
+    redisSetTimeout(c, tv);
+    reply = redisCommand(c, "GET foo");
+    test_cond(s > 0 && reply == NULL && c->err == REDIS_ERR_IO && strcmp(c->errstr, "Resource temporarily unavailable") == 0);
+    freeReplyObject(reply);
+
+    test("Reconnect properly reconnects after a timeout: ");
+    redisReconnect(c);
+    reply = redisCommand(c, "PING");
+    test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS && strcmp(reply->str, "PONG") == 0);
+    freeReplyObject(reply);
+
+    test("Reconnect properly uses owned parameters: ");
+    config.tcp.host = "foo";
+    config.unix.path = "foo";
+    redisReconnect(c);
+    reply = redisCommand(c, "PING");
+    test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS && strcmp(reply->str, "PONG") == 0);
+    freeReplyObject(reply);
+
+    disconnect(c, 0);
+}
+
+static void test_blocking_io_errors(struct config config) {
+    redisContext *c;
+    redisReply *reply;
+    void *_reply;
+    int major, minor;
+
+    /* Connect to target given by config. */
+    c = connect(config);
+    {
+        /* Find out Redis version to determine the path for the next test */
+        const char *field = "redis_version:";
+        char *p, *eptr;
+
+        reply = redisCommand(c,"INFO");
+        p = strstr(reply->str,field);
+        major = strtol(p+strlen(field),&eptr,10);
+        p = eptr+1; /* char next to the first "." */
+        minor = strtol(p,&eptr,10);
+        freeReplyObject(reply);
+    }
+
+    test("Returns I/O error when the connection is lost: ");
+    reply = redisCommand(c,"QUIT");
+    if (major > 2 || (major == 2 && minor > 0)) {
+        /* > 2.0 returns OK on QUIT and read() should be issued once more
+         * to know the descriptor is at EOF. */
+        test_cond(strcasecmp(reply->str,"OK") == 0 &&
+            redisGetReply(c,&_reply) == REDIS_ERR);
+        freeReplyObject(reply);
+    } else {
+        test_cond(reply == NULL);
+    }
+
+    /* On 2.0, QUIT will cause the connection to be closed immediately and
+     * the read(2) for the reply on QUIT will set the error to EOF.
+     * On >2.0, QUIT will return with OK and another read(2) needed to be
+     * issued to find out the socket was closed by the server. In both
+     * conditions, the error will be set to EOF. */
+    assert(c->err == REDIS_ERR_EOF &&
+        strcmp(c->errstr,"Server closed the connection") == 0);
+    redisFree(c);
+
+    c = connect(config);
+    test("Returns I/O error on socket timeout: ");
+    struct timeval tv = { 0, 1000 };
+    assert(redisSetTimeout(c,tv) == REDIS_OK);
+    test_cond(redisGetReply(c,&_reply) == REDIS_ERR &&
+        c->err == REDIS_ERR_IO && errno == EAGAIN);
+    redisFree(c);
+}
+
+static void test_invalid_timeout_errors(struct config config) {
+    redisContext *c;
+
+    test("Set error when an invalid timeout usec value is given to redisConnectWithTimeout: ");
+
+    config.tcp.timeout.tv_sec = 0;
+    config.tcp.timeout.tv_usec = 10000001;
+
+    c = redisConnectWithTimeout(config.tcp.host, config.tcp.port, config.tcp.timeout);
+
+    test_cond(c->err == REDIS_ERR_IO);
+    redisFree(c);
+
+    test("Set error when an invalid timeout sec value is given to redisConnectWithTimeout: ");
+
+    config.tcp.timeout.tv_sec = (((LONG_MAX) - 999) / 1000) + 1;
+    config.tcp.timeout.tv_usec = 0;
+
+    c = redisConnectWithTimeout(config.tcp.host, config.tcp.port, config.tcp.timeout);
+
+    test_cond(c->err == REDIS_ERR_IO);
+    redisFree(c);
+}
+
+static void test_throughput(struct config config) {
+    redisContext *c = connect(config);
+    redisReply **replies;
+    int i, num;
+    long long t1, t2;
+
+    test("Throughput:\n");
+    for (i = 0; i < 500; i++)
+        freeReplyObject(redisCommand(c,"LPUSH mylist foo"));
+
+    num = 1000;
+    replies = malloc(sizeof(redisReply*)*num);
+    t1 = usec();
+    for (i = 0; i < num; i++) {
+        replies[i] = redisCommand(c,"PING");
+        assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_STATUS);
+    }
+    t2 = usec();
+    for (i = 0; i < num; i++) freeReplyObject(replies[i]);
+    free(replies);
+    printf("\t(%dx PING: %.3fs)\n", num, (t2-t1)/1000000.0);
+
+    replies = malloc(sizeof(redisReply*)*num);
+    t1 = usec();
+    for (i = 0; i < num; i++) {
+        replies[i] = redisCommand(c,"LRANGE mylist 0 499");
+        assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_ARRAY);
+        assert(replies[i] != NULL && replies[i]->elements == 500);
+    }
+    t2 = usec();
+    for (i = 0; i < num; i++) freeReplyObject(replies[i]);
+    free(replies);
+    printf("\t(%dx LRANGE with 500 elements: %.3fs)\n", num, (t2-t1)/1000000.0);
+
+    num = 10000;
+    replies = malloc(sizeof(redisReply*)*num);
+    for (i = 0; i < num; i++)
+        redisAppendCommand(c,"PING");
+    t1 = usec();
+    for (i = 0; i < num; i++) {
+        assert(redisGetReply(c, (void*)&replies[i]) == REDIS_OK);
+        assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_STATUS);
+    }
+    t2 = usec();
+    for (i = 0; i < num; i++) freeReplyObject(replies[i]);
+    free(replies);
+    printf("\t(%dx PING (pipelined): %.3fs)\n", num, (t2-t1)/1000000.0);
+
+    replies = malloc(sizeof(redisReply*)*num);
+    for (i = 0; i < num; i++)
+        redisAppendCommand(c,"LRANGE mylist 0 499");
+    t1 = usec();
+    for (i = 0; i < num; i++) {
+        assert(redisGetReply(c, (void*)&replies[i]) == REDIS_OK);
+        assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_ARRAY);
+        assert(replies[i] != NULL && replies[i]->elements == 500);
+    }
+    t2 = usec();
+    for (i = 0; i < num; i++) freeReplyObject(replies[i]);
+    free(replies);
+    printf("\t(%dx LRANGE with 500 elements (pipelined): %.3fs)\n", num, (t2-t1)/1000000.0);
+
+    disconnect(c, 0);
+}
+
+// static long __test_callback_flags = 0;
+// static void __test_callback(redisContext *c, void *privdata) {
+//     ((void)c);
+//     /* Shift to detect execution order */
+//     __test_callback_flags <<= 8;
+//     __test_callback_flags |= (long)privdata;
+// }
+//
+// static void __test_reply_callback(redisContext *c, redisReply *reply, void *privdata) {
+//     ((void)c);
+//     /* Shift to detect execution order */
+//     __test_callback_flags <<= 8;
+//     __test_callback_flags |= (long)privdata;
+//     if (reply) freeReplyObject(reply);
+// }
+//
+// static redisContext *__connect_nonblock() {
+//     /* Reset callback flags */
+//     __test_callback_flags = 0;
+//     return redisConnectNonBlock("127.0.0.1", port, NULL);
+// }
+//
+// static void test_nonblocking_connection() {
+//     redisContext *c;
+//     int wdone = 0;
+//
+//     test("Calls command callback when command is issued: ");
+//     c = __connect_nonblock();
+//     redisSetCommandCallback(c,__test_callback,(void*)1);
+//     redisCommand(c,"PING");
+//     test_cond(__test_callback_flags == 1);
+//     redisFree(c);
+//
+//     test("Calls disconnect callback on redisDisconnect: ");
+//     c = __connect_nonblock();
+//     redisSetDisconnectCallback(c,__test_callback,(void*)2);
+//     redisDisconnect(c);
+//     test_cond(__test_callback_flags == 2);
+//     redisFree(c);
+//
+//     test("Calls disconnect callback and free callback on redisFree: ");
+//     c = __connect_nonblock();
+//     redisSetDisconnectCallback(c,__test_callback,(void*)2);
+//     redisSetFreeCallback(c,__test_callback,(void*)4);
+//     redisFree(c);
+//     test_cond(__test_callback_flags == ((2 << 8) | 4));
+//
+//     test("redisBufferWrite against empty write buffer: ");
+//     c = __connect_nonblock();
+//     test_cond(redisBufferWrite(c,&wdone) == REDIS_OK && wdone == 1);
+//     redisFree(c);
+//
+//     test("redisBufferWrite against not yet connected fd: ");
+//     c = __connect_nonblock();
+//     redisCommand(c,"PING");
+//     test_cond(redisBufferWrite(c,NULL) == REDIS_ERR &&
+//               strncmp(c->error,"write:",6) == 0);
+//     redisFree(c);
+//
+//     test("redisBufferWrite against closed fd: ");
+//     c = __connect_nonblock();
+//     redisCommand(c,"PING");
+//     redisDisconnect(c);
+//     test_cond(redisBufferWrite(c,NULL) == REDIS_ERR &&
+//               strncmp(c->error,"write:",6) == 0);
+//     redisFree(c);
+//
+//     test("Process callbacks in the right sequence: ");
+//     c = __connect_nonblock();
+//     redisCommandWithCallback(c,__test_reply_callback,(void*)1,"PING");
+//     redisCommandWithCallback(c,__test_reply_callback,(void*)2,"PING");
+//     redisCommandWithCallback(c,__test_reply_callback,(void*)3,"PING");
+//
+//     /* Write output buffer */
+//     wdone = 0;
+//     while(!wdone) {
+//         usleep(500);
+//         redisBufferWrite(c,&wdone);
+//     }
+//
+//     /* Read until at least one callback is executed (the 3 replies will
+//      * arrive in a single packet, causing all callbacks to be executed in
+//      * a single pass). */
+//     while(__test_callback_flags == 0) {
+//         assert(redisBufferRead(c) == REDIS_OK);
+//         redisProcessCallbacks(c);
+//     }
+//     test_cond(__test_callback_flags == 0x010203);
+//     redisFree(c);
+//
+//     test("redisDisconnect executes pending callbacks with NULL reply: ");
+//     c = __connect_nonblock();
+//     redisSetDisconnectCallback(c,__test_callback,(void*)1);
+//     redisCommandWithCallback(c,__test_reply_callback,(void*)2,"PING");
+//     redisDisconnect(c);
+//     test_cond(__test_callback_flags == 0x0201);
+//     redisFree(c);
+// }
+
+int main(int argc, char **argv) {
+    struct config cfg = {
+        .tcp = {
+            .host = "127.0.0.1",
+            .port = 6379
+        },
+        .unix = {
+            .path = "/tmp/redis.sock"
+        }
+    };
+    int throughput = 1;
+    int test_inherit_fd = 1;
+
+    /* Ignore broken pipe signal (for I/O error tests). */
+    signal(SIGPIPE, SIG_IGN);
+
+    /* Parse command line options. */
+    argv++; argc--;
+    while (argc) {
+        if (argc >= 2 && !strcmp(argv[0],"-h")) {
+            argv++; argc--;
+            cfg.tcp.host = argv[0];
+        } else if (argc >= 2 && !strcmp(argv[0],"-p")) {
+            argv++; argc--;
+            cfg.tcp.port = atoi(argv[0]);
+        } else if (argc >= 2 && !strcmp(argv[0],"-s")) {
+            argv++; argc--;
+            cfg.unix.path = argv[0];
+        } else if (argc >= 1 && !strcmp(argv[0],"--skip-throughput")) {
+            throughput = 0;
+        } else if (argc >= 1 && !strcmp(argv[0],"--skip-inherit-fd")) {
+            test_inherit_fd = 0;
+        } else {
+            fprintf(stderr, "Invalid argument: %s\n", argv[0]);
+            exit(1);
+        }
+        argv++; argc--;
+    }
+
+    test_format_commands();
+    test_reply_reader();
+    test_blocking_connection_errors();
+    test_free_null();
+
+    printf("\nTesting against TCP connection (%s:%d):\n", cfg.tcp.host, cfg.tcp.port);
+    cfg.type = CONN_TCP;
+    test_blocking_connection(cfg);
+    test_blocking_connection_timeouts(cfg);
+    test_blocking_io_errors(cfg);
+    test_invalid_timeout_errors(cfg);
+    test_append_formatted_commands(cfg);
+    if (throughput) test_throughput(cfg);
+
+    printf("\nTesting against Unix socket connection (%s):\n", cfg.unix.path);
+    cfg.type = CONN_UNIX;
+    test_blocking_connection(cfg);
+    test_blocking_connection_timeouts(cfg);
+    test_blocking_io_errors(cfg);
+    if (throughput) test_throughput(cfg);
+
+    if (test_inherit_fd) {
+        printf("\nTesting against inherited fd (%s):\n", cfg.unix.path);
+        cfg.type = CONN_FD;
+        test_blocking_connection(cfg);
+    }
+
+
+    if (fails) {
+        printf("*** %d TESTS FAILED ***\n", fails);
+        return 1;
+    }
+
+    printf("ALL TESTS PASSED\n");
+    return 0;
+}

+ 42 - 0
ext/hiredis-vip-0.3.0/win32.h

@@ -0,0 +1,42 @@
+#ifndef _WIN32_HELPER_INCLUDE
+#define _WIN32_HELPER_INCLUDE
+#ifdef _MSC_VER
+
+#ifndef inline
+#define inline __inline
+#endif
+
+#ifndef va_copy
+#define va_copy(d,s) ((d) = (s))
+#endif
+
+#ifndef snprintf
+#define snprintf c99_snprintf
+
+__inline int c99_vsnprintf(char* str, size_t size, const char* format, va_list ap)
+{
+    int count = -1;
+
+    if (size != 0)
+        count = _vsnprintf_s(str, size, _TRUNCATE, format, ap);
+    if (count == -1)
+        count = _vscprintf(format, ap);
+
+    return count;
+}
+
+__inline int c99_snprintf(char* str, size_t size, const char* format, ...)
+{
+    int count;
+    va_list ap;
+
+    va_start(ap, format);
+    count = c99_vsnprintf(str, size, format, ap);
+    va_end(ap);
+
+    return count;
+}
+#endif
+
+#endif
+#endif

+ 8 - 1
make-linux.mk

@@ -260,6 +260,13 @@ ifeq ($(ZT_OFFICIAL),1)
 	override LDFLAGS+=-Wl,--wrap=memcpy -static-libstdc++
 endif
 
+ifeq ($(ZT_CONTROLLER),1)
+	LDLIBS+=-L/usr/pgsql-10/lib/ -lpq -Lext/librabbitmq/centos_x64/lib/ -lrabbitmq
+	DEFS+=-DZT_CONTROLLER_USE_LIBPQ
+	INCLUDES+=-Iext/librabbitmq/macos/include -I/usr/pgsql-10/include -Iext/hiredis-vip-0.3.0
+	ONE_OBJS+=ext/hiredis-vip-0.3.0/adlist.o ext/hiredis-vip-0.3.0/async.o ext/hiredis-vip-0.3.0/command.o ext/hiredis-vip-0.3.0/crc16.o ext/hiredis-vip-0.3.0/dict.o ext/hiredis-vip-0.3.0/hiarray.o ext/hiredis-vip-0.3.0/hircluster.o ext/hiredis-vip-0.3.0/hiredis.o ext/hiredis-vip-0.3.0/hiutil.o ext/hiredis-vip-0.3.0/net.o ext/hiredis-vip-0.3.0/read.o ext/hiredis-vip-0.3.0/sds.o
+endif
+
 # ARM32 hell -- use conservative CFLAGS
 ifeq ($(ZT_ARCHITECTURE),3)
 	ifeq ($(shell if [ -e /usr/bin/dpkg ]; then dpkg --print-architecture; fi),armel)
@@ -335,7 +342,7 @@ docker:	FORCE
 	docker build --no-cache -f ext/installfiles/linux/zerotier-containerized/Dockerfile -t zerotier-containerized .
 
 central-controller:	FORCE
-	make -j4 LDLIBS="-L/usr/pgsql-10/lib/ -lpq -Lext/librabbitmq/centos_x64/lib/ -lrabbitmq" CXXFLAGS="-I/usr/pgsql-10/include -I./ext/librabbitmq/centos_x64/include -fPIC" DEFS="-DZT_CONTROLLER_USE_LIBPQ -DZT_CONTROLLER" ZT_OFFICIAL=1 ZT_USE_X64_ASM_ED25519=1 one
+	make -j4 ZT_CONTROLLER=1 ZT_OFFICIAL=1 ZT_USE_X64_ASM_ED25519=1 one
 
 central-controller-docker: FORCE
 	docker build --no-cache -t docker.zerotier.com/zerotier-central/ztcentral-controller:${TIMESTAMP} -f ext/central-controller-docker/Dockerfile --build-arg git_branch=`git name-rev --name-only HEAD` .

+ 5 - 2
make-mac.mk

@@ -1,3 +1,4 @@
+
 CC=clang
 CXX=clang++
 INCLUDES=
@@ -29,7 +30,8 @@ ONE_OBJS+=osdep/MacEthernetTap.o osdep/MacKextEthernetTap.o ext/http-parser/http
 ifeq ($(ZT_CONTROLLER),1)
 	LIBS+=-L/usr/local/opt/libpq/lib -lpq -Lext/librabbitmq/macos/lib -lrabbitmq
 	DEFS+=-DZT_CONTROLLER_USE_LIBPQ -DZT_CONTROLLER
-	INCLUDES+=-Iext/librabbitmq/macos/include -I/usr/local/opt/libpq/include
+	INCLUDES+=-Iext/librabbitmq/macos/include -I/usr/local/opt/libpq/include -Iext/hiredis-vip-0.3.0
+	ONE_OBJS+=ext/hiredis-vip-0.3.0/adlist.o ext/hiredis-vip-0.3.0/async.o ext/hiredis-vip-0.3.0/command.o ext/hiredis-vip-0.3.0/crc16.o ext/hiredis-vip-0.3.0/dict.o ext/hiredis-vip-0.3.0/hiarray.o ext/hiredis-vip-0.3.0/hircluster.o ext/hiredis-vip-0.3.0/hiredis.o ext/hiredis-vip-0.3.0/hiutil.o ext/hiredis-vip-0.3.0/net.o ext/hiredis-vip-0.3.0/read.o ext/hiredis-vip-0.3.0/sds.o
 endif
 
 # Official releases are signed with our Apple cert and apply software updates by default
@@ -150,7 +152,8 @@ official: FORCE
 	make ZT_OFFICIAL_RELEASE=1 mac-dist-pkg
 
 central-controller-docker: FORCE
-	docker build --no-cache -t docker.zerotier.com/zerotier-central/ztcentral-controller:${TIMESTAMP} -f ext/central-controller-docker/Dockerfile --build-arg git_branch=$(shell git name-rev --name-only HEAD) .
+	#docker build --no-cache -t registry.zerotier.com/zerotier-central/ztcentral-controller:${TIMESTAMP} -f ext/central-controller-docker/Dockerfile --build-arg git_branch=$(shell git name-rev --name-only HEAD) .
+	docker build -t registry.zerotier.com/zerotier-central/ztcentral-controller:${TIMESTAMP} -f ext/central-controller-docker/Dockerfile --build-arg git_branch=$(shell git name-rev --name-only HEAD) .
 
 clean:
 	rm -rf MacEthernetTapAgent *.dSYM build-* *.a *.pkg *.dmg *.o node/*.o controller/*.o service/*.o osdep/*.o ext/http-parser/*.o $(CORE_OBJS) $(ONE_OBJS) zerotier-one zerotier-idtool zerotier-selftest zerotier-cli zerotier doc/node_modules macui/build zt1_update_$(ZT_BUILD_PLATFORM)_$(ZT_BUILD_ARCHITECTURE)_*